├── .dockerignore ├── .env.docker.sample ├── .env.sample ├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── Dockerfile ├── LICENSE ├── README.md ├── bower.json ├── config.json ├── docker-compose.yml ├── gulp ├── config.js └── tasks │ ├── bower_JS.js │ ├── bower_angularjs.js │ ├── bower_css.js │ ├── bower_font.js │ ├── build.js │ ├── clean.js │ ├── coffee.js │ ├── coffee_app.js │ ├── coffee_app_background.js │ ├── coffee_app_public.js │ ├── coffee_app_routes.js │ ├── coffee_app_servicescoffee.js │ ├── coffee_lib.js │ ├── coffee_model.js │ ├── default.js │ ├── images_copy.js │ ├── init.js │ ├── jade_copy.js │ ├── loadTaskAll.js │ ├── pug_copy.js │ ├── rev.js │ ├── rev_replace.js │ ├── sass.js │ ├── serve.js │ ├── server_js_copy.js │ ├── setup.js │ ├── task_rev_replace.js │ └── watch.js ├── gulpfile.coffee ├── log4js.setting.js ├── logger.js ├── media ├── danbooru_key.png ├── danbooru_set.png ├── env-1.png ├── env-2.png ├── env-3.png ├── git-bash.png ├── git_1.jpeg ├── git_10.png ├── git_11.png ├── git_12.png ├── git_13.png ├── git_14.png ├── git_15.png ├── git_2.png ├── git_3.png ├── git_4.png ├── git_5.png ├── git_6.png ├── git_7.png ├── git_8.png ├── git_9.png ├── hero_.jpg ├── open-1.png ├── open-2.png ├── project.png ├── start-docker-1.png ├── start-docker-2.png ├── start-docker-3.png ├── start-docker-4.png ├── twitter-1.png ├── twitter-10.png ├── twitter-11.png ├── twitter-12.png ├── twitter-2.png ├── twitter-3.png ├── twitter-4.png ├── twitter-5.png ├── twitter-6-2.png ├── twitter-6.png ├── twitter-7-2.png ├── twitter-7.png ├── twitter-8.png └── twitter-9.png ├── package-lock.json ├── package.json ├── src ├── app │ ├── app.js │ ├── background │ │ ├── development.js │ │ └── production.js │ ├── controllers │ │ ├── ConvertController.js │ │ ├── DashboardController.js │ │ ├── DoneController.js │ │ ├── FavoriteController.js │ │ ├── PostController.js │ │ ├── RecommendController.js │ │ ├── SessionController.js │ │ ├── StatsController.js │ │ ├── TrasnportController.js │ │ ├── UnearthController.js │ │ └── index.js │ ├── express.js │ ├── public │ │ └── front │ │ │ ├── coffee │ │ │ ├── app.coffee │ │ │ ├── common │ │ │ │ ├── controllers.coffee │ │ │ │ ├── directives.coffee │ │ │ │ ├── factories.coffee │ │ │ │ ├── filters.coffee │ │ │ │ └── services.coffee │ │ │ ├── components │ │ │ │ ├── ItemController.coffee │ │ │ │ ├── account │ │ │ │ │ ├── accountContainer.coffee │ │ │ │ │ ├── howToSaveContents.coffee │ │ │ │ │ └── shortcutHelpContainer.coffee │ │ │ │ ├── dashboard │ │ │ │ │ └── dashboard.coffee │ │ │ │ ├── filterTerm.coffee │ │ │ │ ├── find │ │ │ │ │ └── termPagination.coffee │ │ │ │ ├── goToTop.coffee │ │ │ │ ├── history │ │ │ │ │ ├── donePostHistorycoffee.coffee │ │ │ │ │ └── historyHeatmap.coffee │ │ │ │ ├── login │ │ │ │ │ └── allCountContainer.coffee │ │ │ │ ├── post │ │ │ │ │ ├── multiPostContainer.coffee │ │ │ │ │ ├── postContainer.coffee │ │ │ │ │ └── postControlContainerDirective.coffee │ │ │ │ ├── recommends │ │ │ │ │ └── recommendList.coffee │ │ │ │ ├── search │ │ │ │ │ ├── searchBox.coffee │ │ │ │ │ └── searchCondition.coffee │ │ │ │ ├── stats │ │ │ │ │ ├── counter │ │ │ │ │ │ └── statsCounter.coffee │ │ │ │ │ ├── statsCounterContainer.coffee │ │ │ │ │ ├── statsHostnameAggreagationContainer.coffee │ │ │ │ │ ├── statsRankingContainer.coffee │ │ │ │ │ └── statsYearAggregationContainerController.coffee │ │ │ │ ├── tagRegistrationBar.coffee │ │ │ │ └── unearth │ │ │ │ │ ├── unearthTermChanger.coffee │ │ │ │ │ └── unearthTypeChanger.coffee │ │ │ ├── controllers │ │ │ │ ├── FavoriteController.coffee │ │ │ │ ├── FindController.coffee │ │ │ │ ├── LoginController.coffee │ │ │ │ ├── PostListController.coffee │ │ │ │ ├── UnearthController.coffee │ │ │ │ ├── ViewCtrl.coffee │ │ │ │ ├── adminUserController.coffee │ │ │ │ ├── doneHistoryController.coffee │ │ │ │ ├── menuController.coffee │ │ │ │ └── postController.coffee │ │ │ ├── directives │ │ │ │ ├── ZoomImageViewer.coffee │ │ │ │ ├── changeImgWideDirectionDirective.coffee │ │ │ │ ├── fileMode.coffee │ │ │ │ ├── imageLayerControl.coffee │ │ │ │ ├── imgPreload.coffee │ │ │ │ ├── initializeImgWideDirection.coffee │ │ │ │ ├── isActiveNav.coffee │ │ │ │ ├── itemBorder.coffee │ │ │ │ ├── multiSelect.coffee │ │ │ │ ├── onRotateImage.coffee │ │ │ │ ├── onWindowFocus.coffee │ │ │ │ ├── openDone.coffee │ │ │ │ ├── openZoomImageViewer.coffee │ │ │ │ ├── pipeLowToHighBackgroundImage.coffee │ │ │ │ ├── pipeLowToHighImage.coffee │ │ │ │ ├── postManageDirective.coffee │ │ │ │ └── zoomImage.coffee │ │ │ ├── factories │ │ │ │ ├── Data │ │ │ │ │ └── Image.coffee │ │ │ │ ├── ImageViewer.coffee │ │ │ │ ├── Post │ │ │ │ │ ├── DashboardPost.coffee │ │ │ │ │ ├── HistoryPost.coffee │ │ │ │ │ ├── PopularPost.coffee │ │ │ │ │ ├── Post.coffee │ │ │ │ │ ├── RandomPost.coffee │ │ │ │ │ └── YearlyBestPost.coffee │ │ │ │ ├── PostViewingExchanger.coffee │ │ │ │ ├── WindowScrollableSwitcher.coffee │ │ │ │ └── url_parameter_factory │ │ │ │ │ └── URLParameterChecker.coffee │ │ │ ├── itemControlHeader.coffee │ │ │ └── services │ │ │ │ ├── BrowserMeterService.coffee │ │ │ │ ├── EndpointTypeMapperService.coffee │ │ │ │ ├── ImageTransitionService.coffee │ │ │ │ ├── ItemSelectionService.coffee │ │ │ │ ├── NotifyService.coffee │ │ │ │ ├── RecommendService.coffee │ │ │ │ ├── Request │ │ │ │ ├── PostManageService.coffee │ │ │ │ ├── PromiseService.coffee │ │ │ │ ├── RequestService.coffee │ │ │ │ └── TagManageService.coffee │ │ │ │ ├── StatsService.coffee │ │ │ │ ├── Store │ │ │ │ ├── KawpaaLocalStorageService.coffee │ │ │ │ ├── LocalStorageService.coffee │ │ │ │ └── kawpaaStoreService.coffee │ │ │ │ ├── Url │ │ │ │ └── URLParameterService.coffee │ │ │ │ ├── Util │ │ │ │ ├── ArrayService.corffee.coffee │ │ │ │ ├── ImageSizeChecker.coffee │ │ │ │ ├── SecurityService.coffee │ │ │ │ ├── TimeService.coffee │ │ │ │ └── htmlSizeChecker.coffee │ │ │ │ ├── authService.coffee │ │ │ │ ├── commandService.coffee │ │ │ │ ├── configService.coffee │ │ │ │ ├── constatnsService.coffee │ │ │ │ └── termPaginateDataService.coffee │ │ │ ├── images │ │ │ ├── 2015-10-31_19-44-33.jpg │ │ │ ├── F8F8F8.png │ │ │ ├── android-chrome-36x36.png │ │ │ ├── android-chrome-48x48.png │ │ │ ├── android-chrome-72x72.png │ │ │ ├── android-chrome-96x96.png │ │ │ ├── apple-touch-icon-114x114.png │ │ │ ├── apple-touch-icon-120x120.png │ │ │ ├── apple-touch-icon-57x57.png │ │ │ ├── apple-touch-icon-60x60.png │ │ │ ├── apple-touch-icon-72x72.png │ │ │ ├── apple-touch-icon-76x76.png │ │ │ ├── apple-touch-icon-precomposed.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── browserconfig.xml │ │ │ ├── description │ │ │ │ ├── open_option.png │ │ │ │ ├── open_option_kawpaa.png │ │ │ │ ├── option.png │ │ │ │ └── option_kawpaa.png │ │ │ ├── e0e0e0.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── favicon-96x96.png │ │ │ ├── favicon.ico │ │ │ ├── grid.png │ │ │ ├── hero.jpg │ │ │ ├── loaders │ │ │ │ ├── puff.svg │ │ │ │ └── tail-spin.svg │ │ │ ├── login │ │ │ │ ├── _ │ │ │ │ │ ├── pc.jpg │ │ │ │ │ ├── pc_history.jpg │ │ │ │ │ ├── pc_post.jpg │ │ │ │ │ ├── pc_post_s.jpg │ │ │ │ │ └── pc_stats.jpg │ │ │ │ ├── hero.jpg │ │ │ │ ├── pc.jpg │ │ │ │ ├── pc_history.jpg │ │ │ │ ├── pc_post_s.jpg │ │ │ │ ├── pc_stats.jpg │ │ │ │ └── support_sites.jpg │ │ │ ├── manifest.json │ │ │ ├── mstile-150x150.png │ │ │ ├── mstile-310x150.png │ │ │ ├── mstile-70x70.png │ │ │ ├── ogp.png │ │ │ ├── original │ │ │ │ ├── 2015-10-31_19-44-33_j.jpg │ │ │ │ └── login │ │ │ │ │ ├── pc.jpg │ │ │ │ │ ├── pc_history.jpg │ │ │ │ │ ├── pc_post.jpg │ │ │ │ │ ├── pc_post_s.jpg │ │ │ │ │ ├── pc_stats.jpg │ │ │ │ │ └── support_sites.jpg │ │ │ └── thumbnails │ │ │ │ └── default_link_icon.png │ │ │ └── sass │ │ │ ├── _account.scss │ │ │ ├── _ad.scss │ │ │ ├── _best.scss │ │ │ ├── _bootstrap.scss │ │ │ ├── _checkbox.scss │ │ │ ├── _commons.scss │ │ │ ├── _config.scss │ │ │ ├── _dashboard.scss │ │ │ ├── _desktop.scss │ │ │ ├── _entire_style.scss │ │ │ ├── _functions.scss │ │ │ ├── _header.scss │ │ │ ├── _help.scss │ │ │ ├── _history.scss │ │ │ ├── _item.scss │ │ │ ├── _loader.scss │ │ │ ├── _login.scss │ │ │ ├── _mixins.scss │ │ │ ├── _pagination.scss │ │ │ ├── _post.scss │ │ │ ├── _primitive.scss │ │ │ ├── _search.scss │ │ │ ├── _shortcut.scss │ │ │ ├── _sidebar.scss │ │ │ ├── _smartphone.scss │ │ │ ├── _stats.scss │ │ │ ├── _tablet.scss │ │ │ ├── _unearth.scss │ │ │ ├── _variables.scss │ │ │ └── app.scss │ ├── routes │ │ ├── api.js │ │ ├── middlewares │ │ │ ├── auth.js │ │ │ ├── index.js │ │ │ ├── logging.js │ │ │ ├── parameters.js │ │ │ └── session.js │ │ ├── passport.js │ │ ├── routes.js │ │ ├── utils │ │ │ ├── GetRequestParameter.js │ │ │ └── seaquencer.js │ │ └── v1 │ │ │ ├── dashboard.js │ │ │ ├── dones.js │ │ │ ├── favorites.js │ │ │ ├── posts.js │ │ │ ├── publics.js │ │ │ ├── sessions.js │ │ │ ├── stats.js │ │ │ ├── transport.js │ │ │ └── unearth.js │ ├── server.js │ ├── services │ │ ├── done │ │ │ ├── DonePostPairsService.js │ │ │ └── DonePostService.js │ │ ├── favorite │ │ │ └── FavoriteService.js │ │ ├── history │ │ │ └── DoneHistoryService.js │ │ ├── index.js │ │ ├── post │ │ │ ├── DashboardService.js │ │ │ ├── ImageService.js │ │ │ ├── PostRegisterService.js │ │ │ ├── PostService.js │ │ │ └── VideoService.js │ │ ├── stats │ │ │ └── StatsService.js │ │ ├── transport │ │ │ └── TrasnportService.js │ │ └── unearth │ │ │ └── UnearthService.js │ └── views │ │ ├── googlef7315916b4995617.jade │ │ ├── index.jade │ │ ├── layout.jade │ │ ├── partials │ │ ├── account.jade │ │ ├── archive.jade │ │ ├── done.jade │ │ ├── explore.jade │ │ ├── favorite.jade │ │ ├── find.jade │ │ ├── history.jade │ │ ├── index.jade │ │ ├── main.jade │ │ ├── post.jade │ │ ├── stats.jade │ │ ├── stats_ranking.jade │ │ ├── unearth.jade │ │ └── view.jade │ │ └── parts │ │ ├── _how_to_save.jade │ │ ├── _loading_fin.jade │ │ ├── _loading_init.jade │ │ ├── _login.jade │ │ ├── _nav.jade │ │ ├── _post_container.jade │ │ ├── _posts_control_container.jade │ │ ├── _puff_loader.jade │ │ ├── _search_box.jade │ │ ├── _share.jade │ │ ├── _sidebar.jade │ │ ├── head │ │ ├── _favicon.jade │ │ ├── _meta.jade │ │ ├── _script.jade │ │ └── _style.jade │ │ ├── initial_pages │ │ ├── _archive.jade │ │ ├── _done.jade │ │ ├── _factory.jade │ │ ├── _favorite.jade │ │ ├── _find.jade │ │ ├── _history.jade │ │ ├── _inbox.jade │ │ └── _unearth.jade │ │ └── item │ │ ├── _item_control.jade │ │ ├── _item_image.jade │ │ ├── _item_title.jade │ │ └── item_controls │ │ ├── post │ │ ├── _archive.jade │ │ ├── _done.jade │ │ ├── _doneHistory.jade │ │ ├── _find.jade │ │ └── _inbox.jade │ │ └── unearth │ │ ├── _archive.jade │ │ ├── _done.jade │ │ ├── _doneHistory.jade │ │ ├── _inbox.jade │ │ └── _post.jade ├── domains │ ├── data │ │ └── files │ │ │ └── KawpaaImageFile.js │ ├── download │ │ ├── KawpaaCapacity.js │ │ ├── images │ │ │ ├── KawpaaImageDownloader.js │ │ │ ├── KawpaaImageManager.js │ │ │ ├── KawpaaThumbnailGenerator.js │ │ │ └── KawpaaThumbnailManager.js │ │ └── video │ │ │ ├── KawpaaVideoDownloader.js │ │ │ ├── KawpaaVideoManager.js │ │ │ └── VideoThumbnailExtractor.js │ ├── entities │ │ ├── Archive.js │ │ ├── Done.js │ │ ├── Image.js │ │ ├── ImageFile.js │ │ ├── Inbox.js │ │ ├── VideoFile.js │ │ └── index.js │ ├── exceptions │ │ └── httpException.js │ └── proxy │ │ └── KawpaaHttpProxy.js ├── lib │ ├── FileSizeScaler.js │ ├── ImageDownloader.js │ ├── ImageManager.js │ ├── OpenGraphMerger.js │ ├── ThumbnailGenerator.js │ ├── VideoDownloader.js │ ├── constants │ │ └── index.js │ ├── crawlers │ │ ├── FindCategoryPost.js │ │ ├── FindImageCollection.js │ │ ├── FindPostCollection.js │ │ ├── ImageCrawler.js │ │ ├── anime │ │ │ ├── AmatsukaImageCrawler.js │ │ │ ├── DanbooruImageCrawler.js │ │ │ ├── SankakuComplexImageCrawler.js │ │ │ └── YandereImageCrawler.js │ │ ├── index.js │ │ └── shop │ │ │ └── DLSiteCrawler.js │ ├── jobQueue │ │ └── JobQueueManager.js │ ├── my.js │ └── utils │ │ ├── Camera.js │ │ ├── Delayer.js │ │ ├── ImageMeasure.js │ │ ├── Logger.js │ │ ├── SlackLogger.js │ │ ├── TimeManager.js │ │ └── gasyo.js ├── model │ ├── cache │ │ └── redisClient.js │ ├── lib │ │ ├── DatabaseProviderFactory.js │ │ ├── MongoDBTimeNormalizer.js │ │ ├── MongooseFinder.js │ │ ├── MongooseObjectNormalizer.js │ │ ├── PostConditionBuilder.js │ │ └── QueryPropertyTypeGetter.js │ ├── provider │ │ ├── archive.js │ │ ├── base.js │ │ ├── doneHistory.js │ │ ├── donePost.js │ │ ├── favorite.js │ │ ├── image.js │ │ ├── imageRelative.js │ │ ├── index.js │ │ ├── popular.js │ │ ├── post.js │ │ ├── ranking.js │ │ ├── tag.js │ │ ├── user.js │ │ └── video.js │ ├── repository │ │ ├── DashboardRepository.js │ │ ├── DoneHistoryRepository.js │ │ ├── DonePostRepository.js │ │ ├── ImageRepository.js │ │ ├── PostRepository.js │ │ ├── StatisticsRepository.js │ │ ├── TrasnportRepository.js │ │ ├── UserRepository.js │ │ ├── VideoRepository.js │ │ └── index.js │ └── schemas │ │ ├── DoneHistory.js │ │ ├── DonePost.js │ │ ├── Image.js │ │ ├── Post.js │ │ ├── Ranking.js │ │ ├── Tag.js │ │ ├── User.js │ │ ├── Video.js │ │ └── index.js └── script │ ├── crawl_batch │ ├── CrawlBatchForImageBoardExecuter.js │ ├── daily │ │ └── DailyBatchExecuter.js │ ├── monthly │ │ └── MonthlyBatchExecuter.js │ ├── registerUnearth.js │ └── weekly │ │ └── WeeklyBatchExecuter.js │ ├── crons │ ├── imageCrawlerCron.js │ └── index.js │ ├── patch │ └── download-video-from-export-file.js │ └── save-ranking-top.js └── start.bat /.dockerignore: -------------------------------------------------------------------------------- 1 | data 2 | build 3 | node_modules 4 | bower_components 5 | logs 6 | shells 7 | keys 8 | volume 9 | .env -------------------------------------------------------------------------------- /.env.docker.sample: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | PORT=9021 3 | FORCE_HTTP=false 4 | COOKIE_SECRET=kawpaa 5 | APP_NAME=Kawpaa 6 | TW_CK= 7 | TW_CS= 8 | CALLBACK_URL= 9 | MONGODB_URI=mongodb://kawpaa-mongo/kawpaa 10 | REDIS_URI=redis://kawpaa-redis:6379 11 | CRAWL_BATCH_FILEPATH=build/script/crawl_batch 12 | MAX_UPLOADABLE_FILESIZE_MB=100 13 | SHOULD_SUSPEND_REGISTRATION=false 14 | SHOULD_REJECT_REGISTRATION_BY_GENERAL_USER=false 15 | SLACK_LOGGER_CHANNEL= 16 | SLACK_LOGGER_USERNAME= 17 | SLACK_LOGGER_WEBHOOK_URL= 18 | DANBOORU_USERNAME= 19 | DANBOORU_API_KEY= -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | PORT=9021 3 | FORCE_HTTP=false 4 | COOKIE_SECRET=kawpaa 5 | APP_NAME=Kawpaa 6 | TW_CK= 7 | TW_CS= 8 | CALLBACK_URL= 9 | MONGODB_URI=mongodb://127.0.0.1/kawpaa 10 | REDIS_URI=redis://127.0.0.1:6379 11 | CRAWL_BATCH_FILEPATH=build/script/crawl_batch 12 | MAX_UPLOADABLE_FILESIZE_MB=100 13 | SHOULD_SUSPEND_REGISTRATION=false 14 | SHOULD_REJECT_REGISTRATION_BY_GENERAL_USER=false 15 | SLACK_LOGGER_CHANNEL= 16 | SLACK_LOGGER_USERNAME= 17 | SLACK_LOGGER_WEBHOOK_URL= 18 | DANBOORU_USERNAME= 19 | DANBOORU_API_KEY= -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "plugins": [ 4 | "import" 5 | ], 6 | "env": { 7 | "es6": true, 8 | "node": true 9 | }, 10 | "rules": { 11 | "linebreak-style": [ 12 | "off" 13 | ], 14 | "no-underscore-dangle": [ 15 | "off" 16 | ], 17 | } 18 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | bower_components/* 3 | **/lib/*.txt 4 | **/*.txt 5 | data/thumbnails/* 6 | data/images/* 7 | data/videos/* 8 | data/**/* 9 | build/app/accesslog* 10 | build/app/consolelog* 11 | logs/** 12 | volume/** 13 | .env 14 | .env.docker 15 | build 16 | keys -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用できる Node.js デバッグ属性を学ぶために、インテリセンスを使用してください。 3 | // 既存の属性の説明をホバーして表示します。 4 | // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "プログラムの起動", 11 | "program": "${workspaceRoot}/node_modules/gulp/bin/gulp.js", 12 | "args": ["default"] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true, 3 | "search.exclude": { 4 | "**/build": true 5 | }, 6 | "html.format.wrapLineLength": 160, 7 | "prettier.printWidth": 160 8 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015-present eiurur. 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kawpaa", 3 | "version": "1.0.0", 4 | "homepage": "https://github.com/eiurur/kawpaa", 5 | "license": "MIT", 6 | "private": true, 7 | "ignore": [ 8 | "**/.*", 9 | "node_modules", 10 | "bower_components", 11 | "test", 12 | "tests" 13 | ], 14 | "dependencies": { 15 | "bootstrap": "~3.2.0", 16 | "lodash": "~3.9.3", 17 | "moment": "~2.9.0", 18 | "angularjs-toaster": "~0.4.11", 19 | "file-saver-js": "*", 20 | "nginfinitescroll": "~1.2.2", 21 | "animate.css": "animate-css#~3.4.0", 22 | "wow": "~1.1.2", 23 | "notie": "*", 24 | "mousetrap": "~1.5.3", 25 | "angular": "~1.7", 26 | "angular-route": "~1.7", 27 | "angular-animate": "~1.7", 28 | "angular-sanitize": "~1.7", 29 | "angular-touch": "~1.7", 30 | "jszip": "^3.1.3", 31 | "angulartics": "^1.5.0", 32 | "angulartics-google-analytics": "^0.4.0", 33 | "font-awesome": "^4.7.0", 34 | "cal-heatmap": "^3.6.2" 35 | }, 36 | "resolutions": { 37 | "angular": "~1.7.x" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "no_tabs" : { 3 | "level" : "error" 4 | }, 5 | 6 | "no_trailing_whitespace" : { 7 | "level" : "error" 8 | }, 9 | 10 | "max_line_length" : { 11 | "value": 120, 12 | "level" : "error" 13 | }, 14 | 15 | "camel_case_classes" : { 16 | "level" : "error" 17 | }, 18 | 19 | "indentation" : { 20 | "value" : 2, 21 | "level" : "error" 22 | }, 23 | 24 | "no_trailing_semicolons" : { 25 | "level" : "error" 26 | }, 27 | 28 | "no_plusplus" : { 29 | "level" : "error" 30 | }, 31 | 32 | "no_throwing_strings" : { 33 | "level" : "error" 34 | }, 35 | 36 | "cyclomatic_complexity" : { 37 | "value" : 12, 38 | "level" : "ignore" 39 | }, 40 | 41 | "line_endings" : { 42 | "value" : "unix", 43 | "level" : "ignore" 44 | }, 45 | 46 | "no_implicit_parens" : { 47 | "level" : "ignore" 48 | }, 49 | 50 | "no_stand_alone_at" : { 51 | "level": "error" 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | kawpaa-app: 4 | container_name: kawpaa-app 5 | restart: always 6 | build: . 7 | ports: 8 | - '9021:9021' 9 | links: 10 | - kawpaa-redis 11 | - kawpaa-mongo:mongodb 12 | depends_on: 13 | - kawpaa-redis 14 | - kawpaa-mongo 15 | volumes: 16 | - ./volume/app/data:/var/www/myapp/data 17 | environment: 18 | TZ: Asia/Tokyo 19 | WAIT_HOSTS: kawpaa-redis:6379, kawpaa-mongo:27017 20 | logging: 21 | driver: json-file 22 | options: 23 | max-size: 1m 24 | max-file: '5' 25 | kawpaa-mongo: 26 | container_name: kawpaa-mongo 27 | image: 'mongo:4' 28 | restart: always 29 | volumes: 30 | - ./volume/mongo/db:/data/db 31 | - ./volume/mongo/configdb:/data/configdb 32 | kawpaa-redis: 33 | container_name: kawpaa-redis 34 | image: 'redis:5.0.8' 35 | restart: always 36 | -------------------------------------------------------------------------------- /gulp/tasks/bower_JS.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const gulp = require('gulp'); 7 | const bower = require('bower'); 8 | const $ = (require('gulp-load-plugins'))(); 9 | const config = require('../config').bower_js; 10 | 11 | gulp.task('bower_js', () => bower.commands.install().on('end', installed => gulp.src(config.src) 12 | .pipe($.plumber()) 13 | .pipe($.concat('lib.js')) 14 | .pipe(gulp.dest(config.dest)) 15 | .pipe($.rename({suffix: '.min'})) 16 | .pipe($.uglify({mangle: false})) 17 | .pipe(gulp.dest(config.dest)) 18 | .pipe($.gzip()) 19 | .pipe(gulp.dest(config.dest)) 20 | .pipe($.notify('Library Scripts task complete')))); 21 | -------------------------------------------------------------------------------- /gulp/tasks/bower_angularjs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const gulp = require('gulp'); 7 | const bower = require('bower'); 8 | const $ = (require('gulp-load-plugins'))(); 9 | const config = require('../config').bower_angularjs; 10 | 11 | gulp.task('bower_angularjs', () => bower.commands.install().on('end', installed => gulp.src(config.src) 12 | .pipe($.plumber()) 13 | .pipe($.concat('angular-lib.js')) 14 | .pipe(gulp.dest(config.dest)) 15 | .pipe($.rename({suffix: '.min'})) 16 | .pipe($.uglify({mangle: false})) 17 | .pipe(gulp.dest(config.dest)) 18 | .pipe($.gzip()) 19 | .pipe(gulp.dest(config.dest)) 20 | .pipe($.notify('Library AngularJS task complete')))); 21 | -------------------------------------------------------------------------------- /gulp/tasks/bower_css.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const gulp = require('gulp'); 7 | const bower = require('bower'); 8 | const $ = (require('gulp-load-plugins'))(); 9 | const config = require('../config').bower_css; 10 | 11 | gulp.task('bower_css', () => bower.commands.install().on('end', installed => gulp.src(config.src) 12 | .pipe($.plumber()) 13 | .pipe($.concat('lib.css')) 14 | .pipe(gulp.dest(config.dest)) 15 | .pipe($.rename({suffix: '.min'})) 16 | .pipe($.cssmin({mangle: false})) 17 | .pipe(gulp.dest(config.dest)) 18 | .pipe($.gzip()) 19 | .pipe(gulp.dest(config.dest)) 20 | .pipe($.notify('Library CSS task complete')))); -------------------------------------------------------------------------------- /gulp/tasks/bower_font.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const gulp = require('gulp'); 7 | const bower = require('bower'); 8 | const $ = (require('gulp-load-plugins'))(); 9 | const config = require('../config').bower_font; 10 | 11 | gulp.task('bower_font', () => bower.commands.install().on('end', installed => gulp.src(config.src) 12 | .pipe($.plumber()) 13 | .pipe(gulp.dest(config.dest)) 14 | .pipe($.notify('fonts task complete')))); -------------------------------------------------------------------------------- /gulp/tasks/build.js: -------------------------------------------------------------------------------- 1 | require('./loadTaskAll'); 2 | const gulp = require('gulp'); 3 | 4 | const bower_angularjs = require('./bower_angularjs'); 5 | const bower_JS = require('./bower_JS'); 6 | const bower_css = require('./bower_css'); 7 | const bower_font = require('./bower_font'); 8 | const server_js_copy = require('./server_js_copy'); 9 | const coffee_app_public = require('./coffee_app_public'); 10 | const sass = require('./sass'); 11 | const jade_copy = require('./jade_copy'); 12 | const images_copy = require('./images_copy'); 13 | const rev = require('./rev'); 14 | const rev_replace = require('./rev_replace'); 15 | 16 | gulp.task( 17 | 'build', 18 | gulp.series( 19 | 'clean', 20 | 'bower_angularjs', 21 | 'bower_js', 22 | 'bower_css', 23 | 'bower_font', 24 | 'server_js_copy', 25 | 'coffee_app_public', 26 | 'sass', 27 | 'jade_copy', 28 | 'images_copy', 29 | 'rev', 30 | 'rev_replace', 31 | ), 32 | ); 33 | -------------------------------------------------------------------------------- /gulp/tasks/clean.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const gulp = require('gulp'); 7 | const $ = require('gulp-load-plugins')(); 8 | const del = require('del'); 9 | const rimraf = require('rimraf'); 10 | const config = require('../config').clean; 11 | 12 | gulp.task('clean', (cb) => rimraf(config.target, cb)); 13 | // gulp.task('clean', async (done) => { 14 | // try { 15 | // await del(config.targets.concat(config.excludes)); 16 | // } 17 | // catch(e){ 18 | // console.log(e) 19 | // } 20 | // done(); 21 | // }); 22 | -------------------------------------------------------------------------------- /gulp/tasks/coffee.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const path = require('path'); 7 | const gulp = require('gulp'); 8 | const $ = (require('gulp-load-plugins'))(); 9 | const config = require('../config').coffee; 10 | const optFile = path.resolve('config.json'); 11 | 12 | // coffee (src) 13 | gulp.task('coffee', () => gulp.src(config.src, {base: 'build'}) 14 | .pipe($.plumber()) 15 | .pipe($.cached('coffee')) 16 | .pipe($.coffeelint({optFile})) 17 | .pipe($.coffeelint.reporter()) 18 | .pipe($.coffee()) 19 | .pipe(gulp.dest(config.dest)) 20 | .pipe($.notify('coffee task complete'))); -------------------------------------------------------------------------------- /gulp/tasks/coffee_app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const path = require('path'); 7 | const gulp = require('gulp'); 8 | const $ = (require('gulp-load-plugins'))(); 9 | const config = require('../config').coffee_app; 10 | const optFile = path.resolve('config.json'); 11 | 12 | // coffee_app (src) 13 | gulp.task('coffee_app', () => gulp.src(config.src) 14 | .pipe($.plumber()) 15 | .pipe($.cached('coffee_app')) 16 | .pipe($.coffeelint({optFile})) 17 | .pipe($.coffeelint.reporter()) 18 | .pipe($.coffee()) 19 | .pipe(gulp.dest(config.dest)) 20 | .pipe($.notify('coffee_app task complete'))); -------------------------------------------------------------------------------- /gulp/tasks/coffee_app_background.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const path = require('path'); 7 | const gulp = require('gulp'); 8 | const $ = (require('gulp-load-plugins'))(); 9 | const config = require('../config').coffee_app_background; 10 | const optFile = path.resolve('config.json'); 11 | 12 | // coffee_app_background (src) 13 | gulp.task('coffee_app_background', () => gulp.src(config.src) 14 | .pipe($.plumber()) 15 | .pipe($.cached('coffee_app_background')) 16 | .pipe($.coffeelint({optFile})) 17 | .pipe($.coffeelint.reporter()) 18 | .pipe($.coffee()) 19 | .pipe(gulp.dest(config.dest)) 20 | .pipe($.notify('coffee_app_background task complete'))); -------------------------------------------------------------------------------- /gulp/tasks/coffee_app_public.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const path = require('path'); 7 | const gulp = require('gulp'); 8 | const $ = require('gulp-load-plugins')(); 9 | const ngAnnotate = require('gulp-ng-annotate'); 10 | const config = require('../config').coffee_app_public; 11 | 12 | const optFile = path.resolve('config.json'); 13 | 14 | // coffee (src) 15 | gulp.task('coffee', () => 16 | gulp 17 | .src(config.src) 18 | .pipe($.plumber()) 19 | .pipe($.coffeelint({ optFile }))); 20 | 21 | gulp.task('coffee_app_public', () => 22 | gulp 23 | .src(config.src) 24 | .pipe($.plumber()) 25 | .pipe($.cached('coffee_app_public')) 26 | // .pipe($.coffeelint({ optFile })) 27 | // .pipe($.coffeelint.reporter()) 28 | .pipe($.coffee({ bare: true })) 29 | .pipe($.remember('coffee_app_public')) 30 | .pipe($.concat('main.js')) 31 | .pipe(ngAnnotate()) 32 | .pipe(gulp.dest(config.dest)) 33 | .pipe($.stripDebug()) 34 | .pipe($.rename({ suffix: '.min' })) 35 | .pipe($.uglify()) 36 | .pipe(gulp.dest(config.dest)) 37 | .pipe($.gzip()) 38 | .pipe(gulp.dest(config.dest)) 39 | .pipe($.notify('coffee_app_public task complete'))); 40 | -------------------------------------------------------------------------------- /gulp/tasks/coffee_app_routes.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const path = require('path'); 7 | const gulp = require('gulp'); 8 | const $ = (require('gulp-load-plugins'))(); 9 | const config = require('../config').coffee_app_routes; 10 | const optFile = path.resolve('config.json'); 11 | 12 | // coffee_app_routes (src) 13 | gulp.task('coffee_app_routes', () => gulp.src(config.src) 14 | .pipe($.plumber()) 15 | .pipe($.cached('coffee_app_routes')) 16 | .pipe($.coffeelint({optFile})) 17 | .pipe($.coffeelint.reporter()) 18 | .pipe($.coffee()) 19 | .pipe(gulp.dest(config.dest)) 20 | .pipe($.notify('coffee_app_routes task complete'))); -------------------------------------------------------------------------------- /gulp/tasks/coffee_app_servicescoffee.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const path = require('path'); 7 | const gulp = require('gulp'); 8 | const $ = (require('gulp-load-plugins'))(); 9 | const config = require('../config').coffee_app_services; 10 | const optFile = path.resolve('config.json'); 11 | 12 | // coffee_app_services (src) 13 | gulp.task('coffee_app_services', () => gulp.src(config.src) 14 | .pipe($.plumber()) 15 | .pipe($.cached('coffee_app_services')) 16 | .pipe($.coffeelint({optFile})) 17 | .pipe($.coffeelint.reporter()) 18 | .pipe($.coffee()) 19 | .pipe(gulp.dest(config.dest)) 20 | .pipe($.notify('coffee_app_services task complete'))); -------------------------------------------------------------------------------- /gulp/tasks/coffee_lib.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const path = require('path'); 7 | const gulp = require('gulp'); 8 | const $ = (require('gulp-load-plugins'))(); 9 | const config = require('../config').coffee_lib; 10 | const optFile = path.resolve('config.json'); 11 | 12 | // coffee_lib (src) 13 | gulp.task('coffee_lib', () => gulp.src(config.src) 14 | .pipe($.plumber()) 15 | .pipe($.cached('coffee_app_lib')) 16 | .pipe($.coffeelint({optFile})) 17 | .pipe($.coffeelint.reporter()) 18 | .pipe($.coffee()) 19 | .pipe(gulp.dest(config.dest)) 20 | .pipe($.notify('coffee_lib task complete'))); -------------------------------------------------------------------------------- /gulp/tasks/coffee_model.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const path = require('path'); 7 | const gulp = require('gulp'); 8 | const $ = (require('gulp-load-plugins'))(); 9 | const config = require('../config').coffee_model; 10 | const optFile = path.resolve('config.json'); 11 | 12 | // coffee_model (src) 13 | gulp.task('coffee_model', () => gulp.src(config.src) 14 | .pipe($.plumber()) 15 | .pipe($.cached('coffee_app_model')) 16 | .pipe($.coffeelint({optFile})) 17 | .pipe($.coffeelint.reporter()) 18 | .pipe($.coffee()) 19 | .pipe(gulp.dest(config.dest)) 20 | .pipe($.notify('coffee_model task complete'))); -------------------------------------------------------------------------------- /gulp/tasks/default.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const watch = require('./watch'); 3 | const serve = require('./serve'); 4 | 5 | gulp.task('default', gulp.parallel('watch', 'serve')); 6 | -------------------------------------------------------------------------------- /gulp/tasks/images_copy.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const gulp = require('gulp'); 7 | const $ = (require('gulp-load-plugins'))(); 8 | const config = require('../config').images_copy; 9 | 10 | // images copy 11 | gulp.task('images_copy', () => gulp.src(config.src) 12 | .pipe($.plumber()) 13 | .pipe($.changed(config.dest)) 14 | .pipe($.imagemin()) 15 | .pipe(gulp.dest(config.dest)) 16 | .pipe($.notify('images task complete'))); -------------------------------------------------------------------------------- /gulp/tasks/init.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | 3 | gulp.task( 4 | 'init', 5 | gulp.series(gulp.parallel('clean'), () => console.log('init done')), 6 | ); 7 | -------------------------------------------------------------------------------- /gulp/tasks/jade_copy.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const gulp = require('gulp'); 7 | const $ = (require('gulp-load-plugins'))(); 8 | const config = require('../config').jade_copy; 9 | 10 | // jade copy 11 | gulp.task('jade_copy', () => gulp.src(config.src) 12 | .pipe($.plumber()) 13 | .pipe($.changed(config.dest)) 14 | .pipe(gulp.dest(config.dest)) 15 | .pipe($.notify('jade_copy task complete'))); -------------------------------------------------------------------------------- /gulp/tasks/loadTaskAll.js: -------------------------------------------------------------------------------- 1 | require('./bower_JS'); 2 | require('./bower_angularjs'); 3 | require('./bower_css'); 4 | require('./bower_font'); 5 | require('./build'); 6 | require('./clean'); 7 | require('./coffee'); 8 | require('./coffee_app'); 9 | require('./coffee_app_background'); 10 | require('./coffee_app_public'); 11 | require('./coffee_app_routes'); 12 | require('./coffee_app_servicescoffee'); 13 | require('./coffee_lib'); 14 | require('./coffee_model'); 15 | require('./default'); 16 | require('./images_copy'); 17 | require('./init'); 18 | require('./jade_copy'); 19 | require('./pug_copy'); 20 | require('./rev'); 21 | require('./rev_replace'); 22 | require('./sass'); 23 | require('./serve'); 24 | require('./server_js_copy'); 25 | require('./setup'); 26 | require('./task_rev_replace'); 27 | require('./watch'); 28 | -------------------------------------------------------------------------------- /gulp/tasks/pug_copy.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const gulp = require('gulp'); 7 | const $ = (require('gulp-load-plugins'))(); 8 | const config = require('../config').pug_copy; 9 | 10 | // pug copy 11 | gulp.task('pug_copy', () => gulp.src(config.src) 12 | .pipe($.plumber()) 13 | .pipe($.changed(config.dest)) 14 | .pipe(gulp.dest(config.dest)) 15 | .pipe($.notify('pug_copy task complete'))); -------------------------------------------------------------------------------- /gulp/tasks/rev.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const path = require('path'); 7 | const gulp = require('gulp'); 8 | const $ = require('gulp-load-plugins')(); 9 | const config = require('../config').rev; 10 | 11 | gulp.task('rev', f => 12 | gulp 13 | .src(config.src) 14 | .pipe($.rev()) 15 | .pipe(gulp.dest(config.dest)) 16 | .pipe($.rev.manifest()) 17 | .pipe(gulp.dest(config.dest))); 18 | -------------------------------------------------------------------------------- /gulp/tasks/rev_replace.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const fs = require('fs'); 7 | const path = require('path'); 8 | const gulp = require('gulp'); 9 | const $ = require('gulp-load-plugins')(); 10 | const config = require('../config').rev_replace; 11 | const { manifestFileName } = require('../config').rev; 12 | 13 | const manifestFile = path.resolve('build', 'app', 'public', 'front', manifestFileName); 14 | 15 | const addPrefix = function (manifest, prefix) { 16 | const o = {}; 17 | for (const k in manifest) { 18 | const v = manifest[k]; 19 | console.log(k, v); 20 | o[`${prefix}${k}`] = `${prefix}${v}`; 21 | console.log(o); 22 | } 23 | return o; 24 | }; 25 | 26 | gulp.task('rev_replace', (f) => { 27 | console.log(config); 28 | const manifest = gulp.src(manifestFile); 29 | return gulp 30 | .src(config.src) 31 | .pipe($.revReplace({ manifest, replaceInExtensions: ['.jade', '.js', '.css'] })) 32 | .pipe(gulp.dest(config.dest)); 33 | }); 34 | -------------------------------------------------------------------------------- /gulp/tasks/sass.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const gulp = require('gulp'); 7 | const dartSass = require('sass'); 8 | const gulpSass = require('gulp-sass'); 9 | const sass = gulpSass(dartSass); 10 | const $ = require('gulp-load-plugins')(); 11 | const config = require('../config').sass; 12 | 13 | // SASS (src/scss) 14 | gulp.task('sass', () => 15 | gulp 16 | .src(config.src) 17 | .pipe($.plumber()) 18 | .pipe( 19 | sass({ 20 | outputStyle: 'expanded', 21 | }) 22 | ) 23 | .pipe(gulp.dest(config.dest)) 24 | .pipe($.rename({ suffix: '.min' })) 25 | .pipe($.cssmin()) 26 | .pipe(gulp.dest(config.dest)) 27 | .pipe($.gzip()) 28 | .pipe(gulp.dest(config.dest)) 29 | .pipe($.notify('CSS task complete')) 30 | ); 31 | -------------------------------------------------------------------------------- /gulp/tasks/serve.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const node = require('node-dev'); 3 | const config = require('../config').serve; 4 | 5 | gulp.task('serve', (done) => { 6 | const serverPath = `${config.dest}/app.js`; 7 | node(serverPath, [], [], { ignore: [] }); 8 | done(); 9 | }); 10 | -------------------------------------------------------------------------------- /gulp/tasks/server_js_copy.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const gulp = require('gulp'); 7 | const $ = (require('gulp-load-plugins'))(); 8 | const config = require('../config').server_js_copy; 9 | 10 | gulp.task('server_js_copy', () => gulp.src(config.src) 11 | .pipe($.plumber()) 12 | .pipe($.changed(config.dest)) 13 | .pipe(gulp.dest(config.dest)) 14 | .pipe($.notify('server_js_copy task complete'))); -------------------------------------------------------------------------------- /gulp/tasks/setup.js: -------------------------------------------------------------------------------- 1 | require('./loadTaskAll'); 2 | const gulp = require('gulp'); 3 | 4 | gulp.task( 5 | 'setup', 6 | gulp.parallel( 7 | 'bower_angularjs', 8 | 'bower_js', 9 | 'bower_css', 10 | 'bower_font', 11 | 'server_js_copy', 12 | 'coffee_app_public', 13 | 'sass', 14 | 'jade_copy', 15 | 'images_copy', 16 | ), 17 | () => console.log('setup done'), 18 | ); 19 | -------------------------------------------------------------------------------- /gulp/tasks/task_rev_replace.js: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | const gulp = require('gulp'); 7 | const runSequence = require('run-sequence'); 8 | 9 | gulp.task( 10 | 'task_rev_replace', 11 | 12 | gulp.series(gulp.parallel('rev', 'rev_replace'), () => 13 | console.log('task_rev_replace done'), 14 | ), 15 | ); 16 | -------------------------------------------------------------------------------- /gulp/tasks/watch.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const config = require('../config').watch; 3 | 4 | gulp.task('watch', function(done) { 5 | gulp.watch(config.server_js_copy, gulp.task('server_js_copy')); 6 | gulp.watch(config.coffee_app_public, gulp.task('coffee_app_public')); 7 | gulp.watch(config.sass, gulp.task('sass')); 8 | gulp.watch(config.jade_copy, gulp.task('jade_copy')); 9 | gulp.watch(config.images_copy, gulp.task('images_copy')); 10 | done(); 11 | }); 12 | -------------------------------------------------------------------------------- /gulpfile.coffee: -------------------------------------------------------------------------------- 1 | requireDir = require 'require-dir' 2 | requireDir './gulp/tasks', 3 | recursive: true 4 | -------------------------------------------------------------------------------- /log4js.setting.js: -------------------------------------------------------------------------------- 1 | const root = __dirname; 2 | 3 | module.exports = { 4 | appenders: { 5 | stdout: { type: 'stdout' }, 6 | stderr: { type: 'stderr' }, 7 | logfile: { 8 | type: 'dateFile', 9 | pattern: 'yyyyMMdd.log', 10 | filename: `${root}/logs/apps/app`, 11 | alwaysIncludePattern: 'true', 12 | }, 13 | }, 14 | categories: { 15 | default: { 16 | appenders: ['stdout', 'logfile'], 17 | level: 'ALL', 18 | }, 19 | error: { 20 | appenders: ['stderr', 'logfile'], 21 | level: 'error', 22 | }, 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /logger.js: -------------------------------------------------------------------------------- 1 | process.on('unhandledRejection', console.dir); 2 | 3 | const log4js = require('log4js'); 4 | 5 | log4js.configure(require('./log4js.setting.js')); 6 | 7 | const logger = log4js.getLogger(); 8 | 9 | exports.logger = logger; 10 | -------------------------------------------------------------------------------- /media/danbooru_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/danbooru_key.png -------------------------------------------------------------------------------- /media/danbooru_set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/danbooru_set.png -------------------------------------------------------------------------------- /media/env-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/env-1.png -------------------------------------------------------------------------------- /media/env-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/env-2.png -------------------------------------------------------------------------------- /media/env-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/env-3.png -------------------------------------------------------------------------------- /media/git-bash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/git-bash.png -------------------------------------------------------------------------------- /media/git_1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/git_1.jpeg -------------------------------------------------------------------------------- /media/git_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/git_10.png -------------------------------------------------------------------------------- /media/git_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/git_11.png -------------------------------------------------------------------------------- /media/git_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/git_12.png -------------------------------------------------------------------------------- /media/git_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/git_13.png -------------------------------------------------------------------------------- /media/git_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/git_14.png -------------------------------------------------------------------------------- /media/git_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/git_15.png -------------------------------------------------------------------------------- /media/git_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/git_2.png -------------------------------------------------------------------------------- /media/git_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/git_3.png -------------------------------------------------------------------------------- /media/git_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/git_4.png -------------------------------------------------------------------------------- /media/git_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/git_5.png -------------------------------------------------------------------------------- /media/git_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/git_6.png -------------------------------------------------------------------------------- /media/git_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/git_7.png -------------------------------------------------------------------------------- /media/git_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/git_8.png -------------------------------------------------------------------------------- /media/git_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/git_9.png -------------------------------------------------------------------------------- /media/hero_.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/hero_.jpg -------------------------------------------------------------------------------- /media/open-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/open-1.png -------------------------------------------------------------------------------- /media/open-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/open-2.png -------------------------------------------------------------------------------- /media/project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/project.png -------------------------------------------------------------------------------- /media/start-docker-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/start-docker-1.png -------------------------------------------------------------------------------- /media/start-docker-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/start-docker-2.png -------------------------------------------------------------------------------- /media/start-docker-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/start-docker-3.png -------------------------------------------------------------------------------- /media/start-docker-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/start-docker-4.png -------------------------------------------------------------------------------- /media/twitter-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/twitter-1.png -------------------------------------------------------------------------------- /media/twitter-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/twitter-10.png -------------------------------------------------------------------------------- /media/twitter-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/twitter-11.png -------------------------------------------------------------------------------- /media/twitter-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/twitter-12.png -------------------------------------------------------------------------------- /media/twitter-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/twitter-2.png -------------------------------------------------------------------------------- /media/twitter-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/twitter-3.png -------------------------------------------------------------------------------- /media/twitter-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/twitter-4.png -------------------------------------------------------------------------------- /media/twitter-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/twitter-5.png -------------------------------------------------------------------------------- /media/twitter-6-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/twitter-6-2.png -------------------------------------------------------------------------------- /media/twitter-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/twitter-6.png -------------------------------------------------------------------------------- /media/twitter-7-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/twitter-7-2.png -------------------------------------------------------------------------------- /media/twitter-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/twitter-7.png -------------------------------------------------------------------------------- /media/twitter-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/twitter-8.png -------------------------------------------------------------------------------- /media/twitter-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/media/twitter-9.png -------------------------------------------------------------------------------- /src/app/app.js: -------------------------------------------------------------------------------- 1 | const cluster = require('cluster'); 2 | const path = require('path'); 3 | const createListener = require('./express'); 4 | const createServer = require('./server'); 5 | const numCPUs = require('os').cpus().length; 6 | 7 | const { logger } = require(path.resolve('logger')); 8 | 9 | process.on('uncaughtException', (err) => { 10 | logger.info(err); 11 | }); 12 | 13 | (async () => { 14 | /** 15 | * Application 16 | */ 17 | const listener = createListener(); 18 | 19 | /** 20 | * routes, session 21 | */ 22 | require('./routes/passport')(listener); 23 | require('./routes/api')(listener); 24 | require('./routes/routes')(listener); 25 | 26 | /** 27 | * Server 28 | */ 29 | if (process.env.NODE_ENV === 'production' && cluster.isMaster) { 30 | for (let i = 0; i < numCPUs; i++) { 31 | cluster.fork(); 32 | cluster.on('disconnect', () => { 33 | logger.info('disconnect!'); 34 | cluster.fork(); 35 | }); 36 | } 37 | } else { 38 | const server = await createServer(listener); 39 | server.listen(listener.get('port'), '0.0.0.0', () => { 40 | logger.info(`Express HTTPS server listening on port ${listener.get('port')}`); 41 | }); 42 | } 43 | })(); 44 | -------------------------------------------------------------------------------- /src/app/background/development.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const morgan = require('morgan'); 3 | 4 | require('dotenv').config(); 5 | 6 | const { logger } = require(path.resolve('logger')); 7 | 8 | module.exports = function (app) { 9 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; 10 | process.on('unhandledRejection', console.dir); 11 | app.locals.pretty = true; 12 | app.use(morgan('dev')); 13 | logger.debug(process.env); 14 | 15 | return app.use((err, req, res, next) => { 16 | res.status(err.status || 500); 17 | return res.json({ 18 | message: err.message, 19 | error: err, 20 | }); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /src/app/background/production.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const morgan = require('morgan'); 4 | 5 | const { logger } = require(path.resolve('logger')); 6 | 7 | module.exports = (app) => { 8 | // require('newrelic'); 9 | 10 | app.locals.pretty = false; 11 | 12 | // Logging (access) 13 | const FileStreamRotator = require('file-stream-rotator'); 14 | const ACCESS_LOG_DIRECTORY = path.resolve('logs', 'accesslog'); 15 | 16 | // ensure log directory exists 17 | fs.existsSync(ACCESS_LOG_DIRECTORY) || fs.mkdirSync(ACCESS_LOG_DIRECTORY); 18 | 19 | // create a rotating write stream 20 | const accessLogStream = FileStreamRotator.getStream({ 21 | filename: `${ACCESS_LOG_DIRECTORY}/access-%DATE%.log`, 22 | frequency: 'daily', 23 | verbose: false, 24 | date_format: 'YYYY-MM-DD', 25 | }); 26 | 27 | // setup the logger 28 | app.use(morgan('combined', { stream: accessLogStream })); 29 | logger.debug(process.env); 30 | }; 31 | -------------------------------------------------------------------------------- /src/app/controllers/ConvertController.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const Twit = require('twit'); 3 | 4 | require('dotenv').config(); 5 | 6 | const { logger } = require(path.resolve('logger')); 7 | const { seaquencer } = require(path.resolve('build', 'app', 'routes', 'utils', 'seaquencer')); 8 | 9 | module.exports = class ConvertController { 10 | static convertTweetIdToTweetInfo(req, res) { 11 | return seaquencer(req, res, ConvertController.fetchTWeet(req.params)); 12 | } 13 | 14 | static fetchTWeet({ tweetId, user }) { 15 | logger.info('convertTweetIdToTweetInfo ===>'); 16 | logger.info(tweetId, user.twitter_token, user.twitter_token_secret); 17 | const T = new Twit({ 18 | consumer_key: process.env.TW_CK, 19 | consumer_secret: process.env.TW_CS, 20 | access_token: user.twitter_token, 21 | access_token_secret: user.twitter_token_secret, 22 | }); 23 | return T.get(`statuses/show/${tweetId}`, { 24 | include_entities: true, 25 | embeddable: true, 26 | tweet_mode: 'extended', 27 | }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/app/controllers/DashboardController.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { seaquencer } = require(path.resolve('build', 'app', 'routes', 'utils', 'seaquencer')); 4 | const { DashboardService } = require(path.resolve('build', 'app', 'services')); 5 | 6 | module.exports = class DashboardController { 7 | static find(req, res) { 8 | const condition = DashboardService.Condition(req.params); 9 | return seaquencer(req, res, DashboardService.find(condition)); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/app/controllers/DoneController.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { seaquencer } = require(path.resolve('build', 'app', 'routes', 'utils', 'seaquencer')); 4 | const { DonePostService, DonePostPairsService } = require(path.resolve('build', 'app', 'services')); 5 | 6 | module.exports = class PostController { 7 | static findPairs(req, res) { 8 | const condition = DonePostPairsService.Condition(req.params); 9 | return seaquencer(req, res, DonePostPairsService.find(condition)); 10 | } 11 | 12 | static upsert(req, res) { 13 | return seaquencer(req, res, DonePostService.upsert(req.params)); 14 | } 15 | 16 | static flucate(req, res) { 17 | return seaquencer(req, res, DonePostService.fluctuate(req.params)); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/app/controllers/FavoriteController.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { seaquencer } = require(path.resolve('build', 'app', 'routes', 'utils', 'seaquencer')); 4 | const { FavoriteService } = require(path.resolve('build', 'app', 'services')); 5 | 6 | module.exports = class FavoriteController { 7 | static favorite(req, res) { 8 | const condition = FavoriteService.Condition({ 9 | user: req.params.user, 10 | postObjectId: req.params.postObjectId, 11 | favorited: true, 12 | }); 13 | return seaquencer(req, res, FavoriteService.updateById(condition)); 14 | } 15 | 16 | static unfavorite(req, res) { 17 | const condition = FavoriteService.Condition({ 18 | user: req.params.user, 19 | postObjectId: req.params.postObjectId, 20 | favorited: false, 21 | }); 22 | return seaquencer(req, res, FavoriteService.updateById(condition)); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/app/controllers/RecommendController.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const moment = require('moment'); 3 | 4 | const { DLSite } = require('mizu'); 5 | 6 | const redisClient = require(path.resolve('build', 'model', 'cache', 'redisClient')); 7 | 8 | const { seaquencer } = require(path.resolve('build', 'app', 'routes', 'utils', 'seaquencer')); 9 | const takeRandom = (array, n) => array.sort(() => Math.random() - Math.random()).slice(0, n); 10 | module.exports = class RecommendController { 11 | static async find(req, res) { 12 | try { 13 | const yesterday = moment().subtract(1, 'days').format('YYYY-MM-DD'); 14 | const ls = await redisClient.getAsync(yesterday); 15 | if (ls) { 16 | const list = JSON.parse(ls); 17 | if (Array.isArray(list) && list.length) { 18 | return res.send(takeRandom(list, 4)); 19 | } 20 | } 21 | 22 | const initial = { term: 'days' }; 23 | const options = { 24 | type: 'maniax', 25 | category: 'voice', 26 | sub: 'SOU', 27 | affiliateId: 'kawpaa', 28 | limit: 100, 29 | }; 30 | const servive = new DLSite(initial); 31 | const result = await servive.scrape({ 32 | amount: -1, 33 | options, 34 | }); 35 | await redisClient.set(yesterday, JSON.stringify(result)); 36 | 37 | res.send(takeRandom(result, 4)); 38 | } catch (err) { 39 | res.status(err.statusCode).send(err); 40 | } 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /src/app/controllers/SessionController.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const DatabaseProviderFactory = require(path.resolve('build', 'model', 'lib', 'DatabaseProviderFactory')); 4 | 5 | module.exports = class SessionController { 6 | static async get(req, res) { 7 | try { 8 | const session = req.session.passport.user; 9 | 10 | if (typeof session === 'undefined') { 11 | throw new Error('you have not session'); 12 | } 13 | 14 | const params = { twitterIdStr: session.twitterIdStr }; 15 | const userProvider = DatabaseProviderFactory.createProvider('User'); 16 | const user = await userProvider.findByTwitterIdStr(params); 17 | res.send(Object.assign(session, { userObjectId: user._id })); 18 | } catch (err) { 19 | res.status(401).send(err); 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/app/controllers/StatsController.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { StatsService } = require(path.resolve('build', 'app', 'services')); 4 | 5 | module.exports = class StatsController { 6 | static async heads(req, res) { 7 | try { 8 | const service = new StatsService({ userObjectId: req.user._id }); 9 | const heads = await service.heads(); 10 | res.send({ heads }); 11 | } catch (err) { 12 | res.status(err.statusCode).send(err); 13 | } 14 | } 15 | 16 | static async ranking(req, res) { 17 | try { 18 | const service = new StatsService({ userObjectId: req.user._id }); 19 | const { ranking, total } = await service.query({ year: req.params.year }); 20 | res.send({ ranking, total }); 21 | } catch (err) { 22 | res.status(err.statusCode).send(err); 23 | } 24 | } 25 | 26 | static async countByDays(req, res) { 27 | try { 28 | const service = new StatsService({ userObjectId: req.user._id }); 29 | const counts = await service.countByDays({ 30 | begin: req.params.begin, 31 | end: req.params.end, 32 | }); 33 | const heatmapData = {}; 34 | counts.map((item) => { 35 | heatmapData[Math.floor(new Date(item.datetime).getTime() / 1000)] = item.count; 36 | }); 37 | res.send(heatmapData); 38 | } catch (err) { 39 | res.status(err.statusCode).send(err); 40 | } 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /src/app/controllers/TrasnportController.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { seaquencer } = require(path.resolve('build', 'app', 'routes', 'utils', 'seaquencer')); 4 | const { TrasnportService } = require(path.resolve('build', 'app', 'services')); 5 | 6 | module.exports = class TrasnportController { 7 | static export(req, res) { 8 | const { user } = req.params; 9 | return seaquencer(req, res, TrasnportService.export({ user })); 10 | } 11 | static import(req, res) { 12 | const { user, record } = req.params; 13 | return seaquencer(req, res, TrasnportService.import({ user, record })); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/app/controllers/UnearthController.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { seaquencer } = require(path.resolve('build', 'app', 'routes', 'utils', 'seaquencer')); 4 | const { UnearthService } = require(path.resolve('build', 'app', 'services')); 5 | 6 | module.exports = class UnearthController { 7 | static find(req, res) { 8 | const condition = UnearthService.Condition(req.params); 9 | return seaquencer(req, res, UnearthService.find(req.params.type, condition)); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/app/controllers/index.js: -------------------------------------------------------------------------------- 1 | const PostController = require('./PostController'); 2 | const ConvertController = require('./ConvertController'); 3 | const DashboardController = require('./DashboardController'); 4 | const TrasnportController = require('./TrasnportController'); 5 | const DoneController = require('./DoneController'); 6 | const FavoriteController = require('./FavoriteController'); 7 | const RecommendController = require('./RecommendController'); 8 | const SessionController = require('./SessionController'); 9 | const StatsController = require('./StatsController'); 10 | const UnearthController = require('./UnearthController'); 11 | 12 | module.exports = { 13 | PostController, 14 | ConvertController, 15 | DashboardController, 16 | TrasnportController, 17 | DoneController, 18 | FavoriteController, 19 | RecommendController, 20 | SessionController, 21 | StatsController, 22 | UnearthController, 23 | }; 24 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/common/controllers.coffee: -------------------------------------------------------------------------------- 1 | # angular.module "myApp.controllers", [] -------------------------------------------------------------------------------- /src/app/public/front/coffee/common/factories.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.factories" -------------------------------------------------------------------------------- /src/app/public/front/coffee/common/filters.coffee: -------------------------------------------------------------------------------- 1 | # Filters 2 | angular.module "myApp.filters" 3 | .filter "interpolate", (version) -> 4 | (text) -> 5 | String(text).replace /\%VERSION\%/g, version 6 | 7 | .filter "noHTML", -> 8 | (text) -> 9 | if text? 10 | text.replace(//g, '>').replace(/&/, '&') 11 | 12 | .filter 'newlines', ($sce) -> 13 | (text) -> 14 | $sce.trustAsHtml(if text? then text.replace(/\n/g, '
') else '') 15 | 16 | .filter 'trusted', ($sce) -> 17 | (url) -> 18 | $sce.trustAsResourceUrl url 19 | 20 | .filter 'trustedHtml', ($sce) -> 21 | (url) -> 22 | $sce.trustAsHtml url -------------------------------------------------------------------------------- /src/app/public/front/coffee/common/services.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service "CommonService", -> 3 | isLoaded: false 4 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/components/account/howToSaveContents.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.directives" 2 | .directive "howToSaveContents", -> 3 | restrict: 'E' 4 | scope: {} 5 | template: """ 6 |

リンクの追加方法

7 |
リンク(サイトのURL、画像のURL)の追加にはChrome Extensionを利用する必要があります。
8 |
① 以下のサイトからChrome Extensionをインストールしてください。
9 |

10 |

Save to Kawpaa - Github

11 |
12 |
② つぎに、以下のトークンをChrome Extensionのオプションページのフォームに入力してください。
13 |
{{user.twitter_token}}
14 |
15 |
Chrome Extensionのアイコンを右クリックし、メニューを開きます。
オプションをクリックするとオプションページが開かれます。
16 |
③ トークンをコピペしてください。
17 |
これで準備は完了です。
18 |
気に入ったサイトのURLや画像をドンドンストックしていき、QOLを向上させていきましょう。
19 | """ -------------------------------------------------------------------------------- /src/app/public/front/coffee/components/filterTerm.coffee: -------------------------------------------------------------------------------- 1 | # angular.module "myApp.directives" 2 | # .directive 'filterTerm', -> 3 | # restrict: 'E' 4 | # scope: {} 5 | # template: """ 6 | #
7 | #
  • 2015
  • 8 | #
  • 2016
  • 9 | #
  • 2017
  • 10 | #
    11 | # """ 12 | # bindToController: {} 13 | # controller: FilterTermController 14 | # controllerAs: "$ctrl" 15 | 16 | # class FilterTermController 17 | # constructor: (@$scope) -> 18 | # @on() 19 | 20 | # on: -> 21 | # # @$scope.$watch "$ctrl.searchWord", (newData, oldData) => 22 | # # console.log newData, oldData 23 | # # return if newData is oldData 24 | # # @$scope.$emit 'SearchBox::search', searchWord: newData 25 | 26 | # FilterTermController.$inject = ['$scope'] 27 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/components/goToTop.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.directives" 2 | .directive 'goToTop', -> 3 | restrict: 'E' 4 | scope: {} 5 | template: """ 6 | 15 | """ -------------------------------------------------------------------------------- /src/app/public/front/coffee/components/recommends/recommendList.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.directives" 2 | .directive 'recommendList', -> 3 | restrict: 'E' 4 | scope: {} 5 | template: """ 6 |
    7 | 8 | 9 | 10 |
    11 | """ 12 | bindToController: {} 13 | controller: RecommendList 14 | controllerAs: "$ctrl" 15 | 16 | class RecommendList 17 | constructor: (@$scope, @RecommendService) -> 18 | @items = [] 19 | @set() 20 | 21 | set: -> 22 | @RecommendService.find() 23 | .then (response) => @items = response.data 24 | .catch (err) -> console.log err 25 | 26 | RecommendList.$inject = ['$scope', 'RecommendService'] -------------------------------------------------------------------------------- /src/app/public/front/coffee/components/stats/counter/statsCounter.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.directives" 2 | .directive 'statsCounter', -> 3 | restrict: 'E' 4 | scope: {} 5 | template: """ 6 |
    7 |
    8 |
    9 |
    {{$ctrl.type}}
    10 |
    11 | {{$ctrl.count}} 12 | items 13 |
    14 |
    15 |
    16 |
    17 | """ 18 | bindToController: 19 | type: "@" 20 | count: "@" 21 | icon: "@" 22 | controller: statsCounter 23 | controllerAs: "$ctrl" 24 | 25 | class statsCounter 26 | constructor: () -> -------------------------------------------------------------------------------- /src/app/public/front/coffee/components/stats/statsCounterContainer.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.directives" 2 | .directive 'statsCounterContainer', -> 3 | restrict: 'E' 4 | scope: {} 5 | template: """ 6 |
    7 |
    8 | 9 |
    10 |
    11 | """ 12 | bindToController: {} 13 | controller: statsCounterContainer 14 | controllerAs: "$ctrl" 15 | 16 | class statsCounterContainer 17 | constructor: (@$scope, @StatsService, @PromiseService) -> 18 | @items = [] 19 | @types = ['inbox', 'archive', 'done'] 20 | @setCounterDataset() 21 | 22 | setCounterDataset: -> 23 | @fetchCounts() 24 | .then (responses, index) => 25 | responses.map (response, index) => 26 | @items.push 27 | type: @types[index] 28 | count: response.data.count 29 | .catch (err) -> console.log err 30 | 31 | fetchCounts: -> 32 | tasks = @types.map (type) => @PromiseService.get("/api/posts/#{type}/count") 33 | @PromiseService.all(tasks) 34 | 35 | statsCounterContainer.$inject = ['$scope', 'StatsService', 'PromiseService'] -------------------------------------------------------------------------------- /src/app/public/front/coffee/components/stats/statsHostnameAggreagationContainer.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.directives" 2 | .directive 'statsHostnameAggregationContainer', -> 3 | restrict: 'E' 4 | scope: {} 5 | template: """ 6 |
    7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
    hostname回数
    {{item._id}}{{item.sum}}
    21 |
    22 | """ 23 | bindToController: {} 24 | controller: StatsHostnameAggregationContainer 25 | controllerAs: "$ctrl" 26 | 27 | class StatsHostnameAggregationContainer 28 | constructor: (@$scope, @StatsService) -> 29 | @items = [] 30 | @set() 31 | 32 | set: -> 33 | @StatsService.findHostnameAggregation() 34 | .then (response) => @items = response.data.ranking 35 | .catch (err) -> console.log err 36 | 37 | StatsHostnameAggregationContainer.$inject = ['$scope', 'StatsService'] -------------------------------------------------------------------------------- /src/app/public/front/coffee/controllers/FavoriteController.coffee: -------------------------------------------------------------------------------- 1 | # angular.module "myApp.controllers" 2 | # .controller "FavoriteCtrl", ( 3 | # $scope 4 | # Post 5 | # AuthService 6 | # ) -> 7 | 8 | # $scope.user = AuthService.user 9 | # $scope.postList = new Post(AuthService.user.userObjectId, 'favorite') 10 | # $scope.isReady = true 11 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/controllers/LoginController.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.controllers" 2 | .controller "LoginCtrl", ( 3 | $scope 4 | $route 5 | AuthService 6 | NotifyService 7 | ) -> 8 | $scope.email = "" 9 | $scope.password = "" 10 | $scope.token = "" 11 | 12 | $scope.loginWithEmail = () -> 13 | data = { 14 | email: $scope.email, 15 | password: $scope.password, 16 | } 17 | console.log(data) 18 | AuthService.loginWithEmail(data) 19 | .then (response) -> 20 | location.reload(true) 21 | .catch (err) -> 22 | NotifyService.error(err.toString()) 23 | 24 | $scope.loginWithToken = () -> 25 | data = { 26 | token: $scope.token, 27 | password: "dummy", # keyがなかったりvalueがnullだとBadRequestになるのでダミーを渡す 28 | } 29 | AuthService.loginWithToken(data) 30 | .then (response) -> 31 | location.reload(true) 32 | .catch (err) -> 33 | NotifyService.error("Failed login.") 34 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/controllers/ViewCtrl.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.controllers" 2 | .controller "ViewCtrl", ( 3 | $scope 4 | $routeParams 5 | $location 6 | AuthService 7 | PostManageService 8 | ) -> 9 | 10 | $scope.isLoaded = false 11 | $scope.user = AuthService.user -------------------------------------------------------------------------------- /src/app/public/front/coffee/controllers/adminUserController.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.controllers" 2 | .controller "AdminUserCtrl", ( 3 | $scope 4 | $location 5 | AuthService 6 | KawpaaLocalStorageService 7 | ) -> 8 | 9 | $scope.isReady = false 10 | $scope.isAuthenticated = AuthService.status.isAuthenticated 11 | 12 | # ログイン済みで、別ページからの遷移 13 | # if $scope.isAuthenticated 14 | if AuthService.status.isAuthenticated 15 | $scope.isReady = true 16 | return 17 | 18 | AuthService.fetchSession() 19 | .then (response) -> 20 | console.log response.data 21 | 22 | AuthService.status.isAuthenticated = true 23 | AuthService.user = response.data 24 | KawpaaLocalStorageService.set 'userObjectId', response.data.userObjectId 25 | KawpaaLocalStorageService.set 'twitterIdStr', AuthService.user.twitterIdStr 26 | 27 | $scope.user = response.data 28 | $scope.isAuthenticated = true 29 | $scope.isReady = true 30 | .catch (err) -> 31 | new WOW().init() # IndexCntrlのなかで実行しても反応がないのでここで実行 32 | $scope.isAuthenticated = false 33 | $scope.isReady = true 34 | $location.path '/' 35 | return -------------------------------------------------------------------------------- /src/app/public/front/coffee/controllers/doneHistoryController.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.controllers" 2 | .controller "DoneHistoryCtrl", ( 3 | $scope 4 | $location 5 | HistoryPost 6 | TimeService 7 | AuthService 8 | ) -> 9 | 10 | getPost = (param = {}) -> 11 | $scope.date = TimeService.normalizeDate null, param.date 12 | $scope.postList = new HistoryPost(AuthService.user.userObjectId, $scope.date) 13 | $scope.postList.nextPage() 14 | return 15 | 16 | $scope.$on 'doneHistoryCtrl::focusDate', (event, args) -> 17 | console.log 'DoneHistoryCtrl::focusDate on', args 18 | getPost(args) 19 | return 20 | 21 | $scope.$on '$destroy', (event, args) -> 22 | console.log '=======> on desctory DoneHistoryCtrl', args 23 | $scope.postList = null 24 | 25 | $scope.user = AuthService.user 26 | $scope.pageType = 'history' 27 | $scope.isReady = true 28 | getPost() 29 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/controllers/menuController.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.controllers" 2 | .controller "MenuCtrl", ( 3 | $scope 4 | ) -> 5 | $scope.isToggleMenu = false 6 | 7 | $scope.toggle = -> 8 | $scope.isToggleMenu = !$scope.isToggleMenu 9 | 10 | $scope.overlay = -> 11 | if $scope.isToggleMenu 12 | $scope.isToggleMenu = false -------------------------------------------------------------------------------- /src/app/public/front/coffee/controllers/postController.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.controllers" 2 | .controller "PostCtrl", ( 3 | $scope 4 | $routeParams 5 | AuthService 6 | URLParameterService 7 | PostManageService 8 | ) -> 9 | 10 | console.log 'postObjectId = ', $routeParams.postObjectId 11 | console.log 'type = ', $routeParams.type 12 | console.log 'type = ', $routeParams.pageType 13 | 14 | $scope.isLoaded = false 15 | $scope.pageType = $routeParams.pageType 16 | $scope.postObjectId = $routeParams.postObjectId 17 | $scope.user = AuthService.user 18 | 19 | URLParameterService.redirectHomeIfExceptionUrl(4) 20 | {pageType: pageType, postObjectId: postObjectId} = URLParameterService.getParameters('post') 21 | opts = 22 | pageType: pageType 23 | postObjectId: postObjectId 24 | console.log 'opts => ', opts 25 | PostManageService.findByObjectId(opts) 26 | .then (response) -> $scope.post = response.data -------------------------------------------------------------------------------- /src/app/public/front/coffee/directives/changeImgWideDirectionDirective.coffee: -------------------------------------------------------------------------------- 1 | # Directives 2 | angular.module "myApp.directives" 3 | .directive "changeImgWideDirection", -> 4 | restrict: 'A' 5 | link: (scope, element, attrs) -> 6 | toggle = -> 7 | className = element.attr('class') 8 | if className 9 | element.removeClass(className) 10 | else 11 | if element[0].naturalWidth > element[0].naturalHeight 12 | element.addClass(widthWideClass) 13 | else 14 | element.addClass(heightWideClass) 15 | 16 | widthWideClass = 'width--wide' 17 | heightWideClass = 'height--wide' 18 | element.on 'click', -> toggle() 19 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/directives/fileMode.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.directives" 2 | .directive "fileModel", ($parse, PromiseService) -> 3 | restrict: 'A' 4 | link: (scope, element, attrs) -> 5 | model = $parse(attrs.fileModel); 6 | modelSetter = model.assign; 7 | element.on 'change', (e) -> 8 | scope.$apply(()-> 9 | file = element[0].files[0] 10 | modelSetter(scope, file); 11 | ) 12 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/directives/imgPreload.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.directives" 2 | .directive "imgPreload", -> 3 | restrict: "A" 4 | link: (scope, element, attrs) -> 5 | element 6 | .on "load", -> element.addClass "in" 7 | .on "error", -> return 8 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/directives/initializeImgWideDirection.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.directives" 2 | .directive "initializeImgWideDirection", -> 3 | restrict: 'A' 4 | link: (scope, element, attrs) -> 5 | # 縦長の画像だと縦に入りきらず毎回クリックを強いられていたので自動化。横はきれいに収まるので処理不要。 6 | heightWideClass = 'height--wide' 7 | className = element.attr('class') 8 | 9 | # DOMの読み込みが完了しないとwidth, heightが0 10 | element.on 'load', (e) => 11 | if element[0].naturalWidth > element[0].naturalHeight 12 | element.removeClass(className) 13 | else 14 | element.addClass(heightWideClass) 15 | scope.$apply() 16 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/directives/isActiveNav.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.directives" 2 | .directive 'isActiveNav', ($location) -> 3 | restrict: 'A' 4 | link: (scope, element) -> 5 | scope.location = $location 6 | scope.$watch 'location.path()', (currentPath) -> 7 | if currentPath is element[0].attributes['href'].nodeValue 8 | element.addClass 'active' 9 | else 10 | element.removeClass 'active' 11 | return -------------------------------------------------------------------------------- /src/app/public/front/coffee/directives/itemBorder.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.directives" 2 | .directive 'itemBorder', ($location) -> 3 | restrict: 'A' 4 | link: (scope, element, attrs) -> 5 | className = "item__content--#{attrs.type}" 6 | element.addClass className 7 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/directives/onRotateImage.coffee: -------------------------------------------------------------------------------- 1 | # Directives 2 | angular.module "myApp.directives" 3 | .directive "onRotateImage", -> 4 | restrict: 'A' 5 | link: (scope, element, attrs) -> 6 | degreeRotate = 0 7 | Mousetrap.bind ['ctrl+left'], -> element.css('transform', "rotate(#{degreeRotate -= 90}deg)") 8 | Mousetrap.bind ['ctrl+right'], -> element.css('transform', "rotate(#{degreeRotate += 90}deg)") 9 | 10 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/directives/onWindowFocus.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.directives" 2 | .directive 'onWindowFocus', ($rootScope, $window) -> 3 | restrict: 'A' 4 | link: (scope, element) -> 5 | $window.onfocus = -> 6 | console.log ' $windows onfocus' 7 | $rootScope.$broadcast 'onWindowFocus::onfocus', true 8 | return 9 | 10 | $window.onblur = -> 11 | console.log ' $windows onblur' 12 | $rootScope.$broadcast 'onWindowFocus::onblur', true 13 | return 14 | 15 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/directives/openDone.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.directives" 2 | .directive 'openDone', ($location) -> 3 | restrict: 'A' 4 | scope: 5 | posts: '=' 6 | link: (scope, element, attrs) -> 7 | element.on 'click', -> 8 | console.log scope.posts 9 | posts = scope.posts 10 | images = posts.filter((post) -> post.donePost.type is 'image').map((image) -> image.donePost._id).join(',') 11 | links = posts.filter((post) -> post.donePost.type is 'link').map((link) -> link.donePost._id).join(',') 12 | videos = posts.filter((post) -> post.donePost.type is 'video').map((video) -> video.donePost._id).join(',') 13 | texts = posts.filter((post) -> post.donePost.type is 'text').map((text) -> text.donePost._id).join(',') 14 | size = "width=#{screen.availWidth},height=#{screen.availHeight}" 15 | 16 | console.log images, links, videos, texts 17 | if !!images 18 | window.open "/view/done?images=#{images}", '', size 19 | if !!links 20 | window.open "/view/done?&links=#{links}", '', size 21 | if !!videos 22 | window.open "/view/done?videos=#{videos}", '', size 23 | if !!texts 24 | window.open "/view/done?texts=#{texts}", '', size 25 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/directives/openZoomImageViewer.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.directives" 2 | .directive "openZoomImageViewer", ($rootScope, ItemSelectionService) -> 3 | restrict: 'A' 4 | link: (scope, element, attrs) -> 5 | element.on 'click', (e) -> 6 | return if e.ctrlKey || e.shiftKey || ItemSelectionService.isSelected() 7 | 8 | $rootScope.$broadcast 'ZoomImageViewer::show', _id: attrs.id, toImgSrc: attrs.toImgSrc, imgSrc: attrs.imgSrc 9 | return 10 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/directives/pipeLowToHighBackgroundImage.coffee: -------------------------------------------------------------------------------- 1 | # angular.module "myApp.directives" 2 | 3 | # # todo: 配列で渡された画像URLを読み込みが終わり次第差し替えていく 4 | # .directive 'pipeLowToHighBackgroundImage', -> 5 | # restrict: 'A' 6 | # link: (scope, element, attrs) -> 7 | 8 | # fromImg = new Image() 9 | # fromImg.src = attrs.fromImgSrc 10 | # fromImg.onload = -> 11 | # element.css 'background-image', "url('#{attrs.fromImgSrc}')" 12 | # element.addClass 'filter' 13 | # scope.$apply() 14 | # fromImg = null 15 | 16 | # toImg = new Image() 17 | # toImg.src = attrs.toImgSrc 18 | # toImg.onload = -> 19 | # element.css 'background-image', "url('#{attrs.toImgSrc}')" 20 | # element.removeClass 'filter' 21 | # scope.$apply() 22 | # toImg = null 23 | 24 | # toImg.onerror = -> 25 | # console.log 'Failed toImg' 26 | # element.removeClass 'filter' 27 | # scope.$apply() 28 | # toImg = null -------------------------------------------------------------------------------- /src/app/public/front/coffee/directives/pipeLowToHighImage.coffee: -------------------------------------------------------------------------------- 1 | # todo: 配列で渡された画像URLを読み込みが終わり次第差し替えていく 2 | angular.module "myApp.directives" 3 | .directive 'pipeLowToHighImage', -> 4 | restrict: 'A' 5 | link: (scope, element, attrs) -> 6 | element.addClass 'filter' 7 | toImg = new Image() 8 | toImg.onload = -> 9 | element.attr 'src', attrs.toImgSrc 10 | element.removeClass 'filter' 11 | scope.$apply() 12 | toImg = null 13 | 14 | toImg.onerror = -> 15 | console.log 'Failed toImg' 16 | element.attr 'src', './front/images/loaders/puff.svg' # Loaderを表示。(TODO: 一極化) 17 | element.removeClass 'filter' 18 | scope.$apply() 19 | toImg = null 20 | 21 | toImg.src = attrs.toImgSrc 22 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/factories/Data/Image.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.factories" 2 | .factory 'Image', -> 3 | class Image 4 | constructor: (@width, @height, @from, @to) -> 5 | 6 | Image -------------------------------------------------------------------------------- /src/app/public/front/coffee/factories/Post/DashboardPost.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.factories" 2 | .factory 'DashboardPost', (Post, $interval, $timeout) -> 3 | class DashboardPost extends Post 4 | 5 | constructor: (hostnames) -> 6 | super(null, 'dashboard') # findのユーザIDは特定のユーザの投稿ではないのでnull 7 | @sort = 'createdAtDesc' 8 | @endpoint = '/api/dashboard' 9 | @hostnames = hostnames 10 | @masonry = new MiniMasonry({container: '#macy'}) 11 | 12 | getQueryString: -> 13 | limit: @limit 14 | skip: @skip 15 | filter: @filter 16 | sort: @sort 17 | hostnames: @hostnames 18 | 19 | afterAdded: -> 20 | $timeout => 21 | @recalcLayout() 22 | $timeout => 23 | hides = angular.element('#macy').find('.dashboard__post:not(.show)') 24 | angular.forEach hides, (element) => 25 | ele = angular.element(element) 26 | ele.addClass('show') 27 | @recalcLayout() 28 | , 1000 29 | , 66 30 | 31 | recalcLayout: -> 32 | if @masonry then @masonry.layout() 33 | 34 | DashboardPost 35 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/factories/Post/PopularPost.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.factories" 2 | .factory 'PopularPost', (Post) -> 3 | class PopularPost extends Post 4 | 5 | constructor: (term, date) -> 6 | super(null, 'find') # findのユーザIDは特定のユーザの投稿ではないのでnull 7 | @term = term 8 | @date = date 9 | @sort = 'siteNameAsc' # DLSiteのコンテンツのダウンロードが早く、デフォルトのupdatedAtだと必ず最下部に来てしまい、なんとなく見栄え悪し。なのでsiteName順に変更。 10 | 11 | getQueryString: -> 12 | type: @type 13 | limit: @limit 14 | skip: @skip 15 | filter: @filter 16 | sort: @sort 17 | term: @term 18 | date: @date 19 | 20 | PopularPost 21 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/factories/WindowScrollableSwitcher.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.factories" 2 | .factory 'WindowScrollableSwitcher', -> 3 | class WindowScrollableSwitcher 4 | constructor: -> 5 | 6 | # @preventDefault: (e) -> 7 | # e.preventDefault() 8 | # # e = e || window.event 9 | # # if e.preventDefault 10 | # # e.preventDefault() 11 | # # e.returnValue = false 12 | 13 | @enableScrolling: -> 14 | window.onscroll = -> 15 | return 16 | 17 | @disableScrolling: -> 18 | x = window.scrollX 19 | y = window.scrollY 20 | window.onscroll = -> 21 | window.scrollTo x, y 22 | 23 | WindowScrollableSwitcher -------------------------------------------------------------------------------- /src/app/public/front/coffee/factories/url_parameter_factory/URLParameterChecker.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.factories" 2 | .factory 'URLParameterChecker', (URLParameterService) -> 3 | class URLParameterChecker 4 | constructor: -> 5 | @urlResources = URLParameterService.parse() 6 | @queryParams = URLParameterService.getQueryString() 7 | 8 | checkURLResourceLength: (allowableLength) -> 9 | URLParameterService.checkURLResourceLength(@urlResources.length, allowableLength) 10 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/BrowserMeterService.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service "BrowserMeterService", -> 3 | getBrowserDimensions: -> 4 | width: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth 5 | height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight 6 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/EndpointTypeMapperService.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service "EndpointTypeMapperService", -> 3 | types: 4 | inbox: 'inboxes' 5 | archive: 'archives' 6 | done: 'dones' 7 | history: 'histroies' 8 | get: (type) -> 9 | console.log type 10 | console.log @types 11 | console.log @types[type] 12 | unless @types[type] # inboxページはtypeがnull 13 | return @types.inbox 14 | else if @types[type] 15 | return @types[type] 16 | else 17 | throw new Error("#{type} is not accpepted") -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/ImageTransitionService.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service "ImageTransitionService", () -> 3 | post: 4 | from: 'tiny' 5 | middle: 'small' 6 | to: 'medium' 7 | zoomingPost: 8 | from: 'medium' 9 | middle: '' 10 | to: 'original' 11 | stats: 12 | from:'tiny' 13 | middle: '' 14 | to: 'original' -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/NotifyService.coffee: -------------------------------------------------------------------------------- 1 | # Services 2 | angular.module "myApp.services" 3 | .service 'NotifyService', -> 4 | success: (text) -> 5 | notie.alert( 6 | type: 'success' 7 | text: text 8 | time: 1.5 9 | ) 10 | 11 | warning: (text) -> 12 | notie.alert( 13 | type: 'warning' 14 | text: text 15 | time: 1.5 16 | ) 17 | 18 | error: (text) -> 19 | notie.alert( 20 | type: 'error' 21 | text: text 22 | time: 1.5 23 | ) 24 | 25 | infomation: (text) -> 26 | notie.alert( 27 | type: 'info' 28 | text: text 29 | time: 1.5 30 | ) 31 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/RecommendService.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service "RecommendService", (PromiseService) -> 3 | find: (params) -> 4 | url = "/api/recommends" 5 | PromiseService.get url 6 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/Request/PromiseService.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service "PromiseService", ($q, RequestService) -> 3 | post: (url, payload, opts = {}) -> 4 | return $q (resolve, reject) -> 5 | RequestService.post url, payload, opts 6 | .then (response) -> resolve response 7 | .catch (err) -> reject err 8 | 9 | get: (url, opts = {}) -> 10 | return $q (resolve, reject) -> 11 | RequestService.get url, opts 12 | .then (response) -> resolve response 13 | .catch (err) -> reject err 14 | 15 | put: (url, payload, opts = {}) -> 16 | return $q (resolve, reject) -> 17 | RequestService.put url, payload, opts 18 | .then (response) -> resolve response 19 | .catch (err) -> reject err 20 | 21 | delete: (url) -> 22 | return $q (resolve, reject) -> 23 | RequestService.delete url 24 | .then (response) -> resolve response 25 | .catch (err) -> reject err 26 | 27 | all: (tasks) -> 28 | return $q.all(tasks) -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/Request/RequestService.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service "RequestService", ($http) -> 3 | post: (url, payload, opts = {}) -> 4 | $http.post url, payload, opts 5 | 6 | get: (url, opts = {}) -> 7 | $http.get url, opts 8 | 9 | put: (url, payload, opts = {}) -> 10 | $http.put url, payload, opts 11 | 12 | delete: (url, payload) -> 13 | $http.delete url, 14 | data: payload 15 | headers: 'Content-Type': 'application/json;charset=utf-8' -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/Request/TagManageService.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service "TagManageService", ($q, NotifyService, PromiseService, RequestService) -> 3 | register: (params) -> 4 | return $q (resolve, reject) -> 5 | RequestService.put "/api/tag/#{params.postObjectId}", tags: params.tags 6 | .then (response) -> 7 | NotifyService.success 'タグを追加しました' 8 | return resolve response.data 9 | .catch (err) -> 10 | NotifyService.error err.statusText 11 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/StatsService.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service "StatsService", (PromiseService) -> 3 | terms: -> 4 | LAUNCHED_YEAR = 2015 # サービス開始年 5 | currentYear = (new Date()).getFullYear() 6 | [LAUNCHED_YEAR...currentYear].reverse() 7 | 8 | findHostnameAggregation: (params) -> 9 | url = "/api/stats/hostname/list" 10 | PromiseService.get url 11 | 12 | heads: (params) -> 13 | url = "/api/stats/ranking/heads" 14 | PromiseService.get url 15 | 16 | findByYear: (params) -> 17 | url = "/api/stats/ranking/#{params.year}" 18 | PromiseService.get url 19 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/Store/KawpaaLocalStorageService.coffee: -------------------------------------------------------------------------------- 1 | # Services 2 | angular.module "myApp.services" 3 | .service "KawpaaLocalStorageService", (LocalStorageService) -> 4 | set: (key, value) -> LocalStorageService.set "Kawpaa:#{key}", value 5 | get: (key) -> LocalStorageService.get "Kawpaa:#{key}" 6 | isEmpty: (key) -> LocalStorageService.get("Kawpaa:#{key}") is null 7 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/Store/LocalStorageService.coffee: -------------------------------------------------------------------------------- 1 | # Services 2 | angular.module "myApp.services" 3 | .service "LocalStorageService", -> 4 | 5 | set: (key, value) -> 6 | localStorage.setItem key, value 7 | 8 | get: (key) -> 9 | localStorage.getItem key -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/Store/kawpaaStoreService.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service "KawpaaStoreService", -> 3 | store: {} 4 | set: (key, value) -> @store[key] = value 5 | get: (key) -> @store[key] 6 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/Util/ArrayService.corffee.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service "ArrayService", -> 3 | takeRandom: (arr) -> 4 | arr[Math.floor(Math.random() * arr.length)] 5 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/Util/ImageSizeChecker.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service 'ImageSizeChecker', -> 3 | # NOTE: image = myApp.factories.Image x_x < 型が欲しい 4 | isVerticallyLong: (image, imgDOM, htmlDOM) -> 5 | htmlDOM = htmlDOM or angular.element(document).find('html') 6 | 7 | if image.width and image.height 8 | w = image.width 9 | h = image.height 10 | else # DBに画像のサイズ情報がない場合は、読みこんだ画像からサイズを取得 11 | h = imgDOM[0].naturalHeight 12 | w = imgDOM[0].naturalWidth 13 | 14 | h_w_percent = h / w * 100 15 | 16 | cH = htmlDOM[0].clientHeight 17 | cW = htmlDOM[0].clientWidth 18 | cH_cW_percent = cH / cW * 100 19 | 20 | return h_w_percent - cH_cW_percent >= 0 -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/Util/SecurityService.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service "SecurityService", ($sce) -> 3 | trustAsResourcUrl: (url) -> 4 | $sce.trustAsResourceUrl(url) -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/Util/TimeService.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service "TimeService", -> 3 | type: 4 | DAYS: 'days' 5 | WEEKS: 'weeks' 6 | MONTHS: 'months' 7 | YEARS: 'years' 8 | 9 | normalizeDate: (term, date) -> 10 | return null unless date 11 | switch term 12 | when this.type.DAYS then return moment(date).format('YYYY-MM-DD') 13 | when this.type.WEEKS then return moment(date).format('YYYY-MM-DD') 14 | when this.type.MONTHS then return moment(date).date('1').format('YYYY-MM-DD') 15 | else return moment(date).format('YYYY-MM-DD') 16 | 17 | changeDate: (term, date, amount) -> 18 | return null unless date 19 | switch term 20 | when this.type.DAYS then return moment(date).add(amount, 'days').format('YYYY-MM-DD') 21 | when this.type.WEEKS then return moment(date).add(amount, 'weeks').format('YYYY-MM-DD') 22 | when this.type.MONTHS then return moment(date).add(amount, 'months').date('1').format('YYYY-MM-DD') 23 | else return moment().format('YYYY-MM-DD') 24 | 25 | agos: () -> 26 | LAUNCHED_YEAR = 2015 # サービス開始年 27 | currentYear = (new Date()).getFullYear() 28 | diffYear = currentYear - LAUNCHED_YEAR 29 | return [1..diffYear] -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/Util/htmlSizeChecker.coffee: -------------------------------------------------------------------------------- 1 | # 未使用 2 | angular.module "myApp.services" 3 | .service 'htmlSizeChecker', -> 4 | isVerticallyLong: (htmlDOM) -> 5 | htmlDOM = htmlDOM or angular.element(document).find('html') 6 | cH = htmlDOM[0].clientHeight 7 | cW = htmlDOM[0].clientWidth 8 | return cH - cW >= 0 -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/authService.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service "AuthService", (PromiseService) -> 3 | fetchSession: -> 4 | PromiseService.get "/api/sessions" 5 | 6 | loginWithEmail: (data) -> 7 | PromiseService.post "/auth/signinWithEmail", data 8 | 9 | loginWithToken: (data)-> 10 | PromiseService.post "/auth/signinWithToken", data 11 | 12 | status: isAuthenticated: false 13 | 14 | user: {} 15 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/commandService.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service "CommandService", -> 3 | commands: [ 4 | { 5 | name: 'アーカイブに保存する' 6 | key: 'a' 7 | } 8 | { 9 | name: 'Inboxに保存する / Inboxに戻す' 10 | key: 'i' 11 | } 12 | { 13 | name: '射精した' 14 | key: 'd' 15 | } 16 | { 17 | name: 'お気に入りに追加する / お気に入りから削除する' 18 | key: 'f' 19 | } 20 | { 21 | name: '新しいウィンドウで開く' 22 | key: 'o' 23 | } 24 | { 25 | name: '削除する' 26 | key: 'r' 27 | } 28 | { 29 | name: '拡大/縮小' 30 | key: 'z' 31 | } 32 | { 33 | name: '次のポストに移動する' 34 | key: 'j, →' 35 | } 36 | { 37 | name: '前のポストに移動する' 38 | key: 'k, ←' 39 | } 40 | { 41 | name: '画像を右に90°回転させる' 42 | key: 'ctrl + →' 43 | } 44 | { 45 | name: '画像を左に90°回転させる' 46 | key: 'ctrl + ←' 47 | } 48 | { 49 | name: '閉じる' 50 | key: 'Esc, q' 51 | } 52 | { 53 | name: '期間の変更' 54 | key: 'p p' 55 | } 56 | { 57 | name: 'フィルタタイプ切り替え' 58 | key: 't t' 59 | } 60 | { 61 | name: 'ソート切り替え' 62 | key: 's s' 63 | } 64 | { 65 | name: 'ショートカットキーのヘルプを開く / 閉じる' 66 | key: 'h' 67 | } 68 | ] 69 | -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/configService.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service "ConfigService", () -> 3 | config: {} -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/constatnsService.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service "ConstantsService", -> 3 | 4 | DURING_LIST: { 5 | items: do -> 6 | LAUNCHED_YEAR = 2015 # サービス開始年 7 | currentYear = (new Date()).getFullYear() 8 | selectedYears = [currentYear..LAUNCHED_YEAR].map (year) -> {k: "#{year}年", v: year} 9 | [{k: '全期間', v: 'all'}].concat(selectedYears) 10 | selected: {k: '全期間', v: 'all'} 11 | } 12 | 13 | FILTER_TYPE_LIST: { 14 | items: [ 15 | {k: '全て', v: 'all'} 16 | {k: 'Image', v: 'image'} 17 | {k: 'Link', v: 'link'} 18 | {k: 'Video', v: 'video'} 19 | {k: 'Text', v: 'text'} 20 | ] 21 | selected: {k: '全て', v: 'all'} 22 | } 23 | 24 | SORT_TYPE_LIST: { 25 | items: [ 26 | {k: '抜いた数が多い順', v: 'numDoneDesc', t: 'done,favorite'} 27 | {k: '抜いた数が少ない順', v: 'numDoneAsc', t: 'done,favorite'} 28 | {k: '更新が新しい順', v: 'updatedAtDesc', t: 'all'} 29 | {k: '更新が古い順', v: 'updatedAtAsc', t: 'all'} 30 | {k: '登録が新しい順', v: 'createdAtDesc', t: 'all'} 31 | {k: '登録が古い順', v: 'createdAtAsc', t: 'all'} 32 | ] 33 | selected: {k: '更新が新しい順', v: 'updatedAtDesc'} 34 | } -------------------------------------------------------------------------------- /src/app/public/front/coffee/services/termPaginateDataService.coffee: -------------------------------------------------------------------------------- 1 | angular.module "myApp.services" 2 | .service "TermPeginateDataServicve", ($rootScope) -> 3 | publish: (params) -> 4 | $rootScope.$broadcast 'TermPeginateDataServicve::publish', params -------------------------------------------------------------------------------- /src/app/public/front/images/2015-10-31_19-44-33.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/2015-10-31_19-44-33.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/F8F8F8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/F8F8F8.png -------------------------------------------------------------------------------- /src/app/public/front/images/android-chrome-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/android-chrome-36x36.png -------------------------------------------------------------------------------- /src/app/public/front/images/android-chrome-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/android-chrome-48x48.png -------------------------------------------------------------------------------- /src/app/public/front/images/android-chrome-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/android-chrome-72x72.png -------------------------------------------------------------------------------- /src/app/public/front/images/android-chrome-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/android-chrome-96x96.png -------------------------------------------------------------------------------- /src/app/public/front/images/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /src/app/public/front/images/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /src/app/public/front/images/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /src/app/public/front/images/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /src/app/public/front/images/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /src/app/public/front/images/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /src/app/public/front/images/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /src/app/public/front/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/apple-touch-icon.png -------------------------------------------------------------------------------- /src/app/public/front/images/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | #da532c 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/app/public/front/images/description/open_option.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/description/open_option.png -------------------------------------------------------------------------------- /src/app/public/front/images/description/open_option_kawpaa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/description/open_option_kawpaa.png -------------------------------------------------------------------------------- /src/app/public/front/images/description/option.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/description/option.png -------------------------------------------------------------------------------- /src/app/public/front/images/description/option_kawpaa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/description/option_kawpaa.png -------------------------------------------------------------------------------- /src/app/public/front/images/e0e0e0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/e0e0e0.png -------------------------------------------------------------------------------- /src/app/public/front/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/favicon-16x16.png -------------------------------------------------------------------------------- /src/app/public/front/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/favicon-32x32.png -------------------------------------------------------------------------------- /src/app/public/front/images/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/favicon-96x96.png -------------------------------------------------------------------------------- /src/app/public/front/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/favicon.ico -------------------------------------------------------------------------------- /src/app/public/front/images/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/grid.png -------------------------------------------------------------------------------- /src/app/public/front/images/hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/hero.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/loaders/tail-spin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/app/public/front/images/login/_/pc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/login/_/pc.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/login/_/pc_history.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/login/_/pc_history.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/login/_/pc_post.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/login/_/pc_post.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/login/_/pc_post_s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/login/_/pc_post_s.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/login/_/pc_stats.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/login/_/pc_stats.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/login/hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/login/hero.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/login/pc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/login/pc.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/login/pc_history.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/login/pc_history.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/login/pc_post_s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/login/pc_post_s.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/login/pc_stats.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/login/pc_stats.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/login/support_sites.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/login/support_sites.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "My app", 3 | "icons": [ 4 | { 5 | "src": "\/android-chrome-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-chrome-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-chrome-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-chrome-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /src/app/public/front/images/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/mstile-150x150.png -------------------------------------------------------------------------------- /src/app/public/front/images/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/mstile-310x150.png -------------------------------------------------------------------------------- /src/app/public/front/images/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/mstile-70x70.png -------------------------------------------------------------------------------- /src/app/public/front/images/ogp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/ogp.png -------------------------------------------------------------------------------- /src/app/public/front/images/original/2015-10-31_19-44-33_j.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/original/2015-10-31_19-44-33_j.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/original/login/pc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/original/login/pc.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/original/login/pc_history.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/original/login/pc_history.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/original/login/pc_post.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/original/login/pc_post.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/original/login/pc_post_s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/original/login/pc_post_s.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/original/login/pc_stats.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/original/login/pc_stats.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/original/login/support_sites.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/original/login/support_sites.jpg -------------------------------------------------------------------------------- /src/app/public/front/images/thumbnails/default_link_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/images/thumbnails/default_link_icon.png -------------------------------------------------------------------------------- /src/app/public/front/sass/_account.scss: -------------------------------------------------------------------------------- 1 | .profile__container { 2 | word-break: break-all; 3 | word-wrap: break-word; 4 | max-width: 640px; 5 | @include plenty(); 6 | @include media(); 7 | } 8 | 9 | .user-profile__conainter { 10 | margin: 30px 0; 11 | @include media(); 12 | } 13 | 14 | .user-info__container { 15 | max-width: 640px; 16 | } 17 | 18 | .import-export { 19 | display: flex; 20 | flex-direction: column; 21 | & > div { 22 | display: flex; 23 | flex-direction: row; 24 | justify-content: space-between; 25 | & + div { 26 | margin-top: 2rem; 27 | } 28 | i { 29 | line-height: 1.4; 30 | margin-right: 1rem; 31 | display: none; 32 | } 33 | } 34 | .download { 35 | display: flex; 36 | align-content: center; 37 | &.disabled { 38 | i { 39 | display: flex; 40 | } 41 | } 42 | } 43 | .upload { 44 | display: flex; 45 | align-content: center; 46 | &.disabled { 47 | i { 48 | display: flex; 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/app/public/front/sass/_ad.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | .ads { 4 | padding: 0 0 3rem; 5 | img { 6 | height: auto; 7 | width: 100%; 8 | } 9 | img.dummy { 10 | height: 1px; 11 | width: 1px; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/public/front/sass/_checkbox.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | /** 4 | * 擬似的なチェックボックス(ボタンスタイル) 5 | */ 6 | .checkbox__container { 7 | display: flex; 8 | 9 | & li { 10 | list-style: none; 11 | margin-right: 15px; 12 | flex-direction: row; 13 | border: 1px solid #b0e4ea; //TODO 14 | padding: 0.4rem 3rem; 15 | border-radius: 4rem; 16 | font-size: 2rem; 17 | cursor: pointer; 18 | color: #b0e4ea; //TODO 19 | transition: all ease 0.2s; 20 | 21 | &:hover { 22 | color: white; //TODO 23 | background: #b0e4ea; //TODO 24 | } 25 | 26 | &[data-checked] { 27 | color: black; //TODO 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/public/front/sass/_config.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | -------------------------------------------------------------------------------- /src/app/public/front/sass/_desktop.scss: -------------------------------------------------------------------------------- 1 | @media (min-width: 993px) { 2 | .header-container { 3 | .header { 4 | height: 0px; 5 | } 6 | } 7 | nav { 8 | display: none; 9 | } 10 | 11 | /** 12 | * share icons 13 | */ 14 | .share-icons { 15 | position: absolute; 16 | display: flex; 17 | justify-content: space-between; 18 | bottom: 3rem; 19 | left: 0; 20 | right: 0; 21 | list-style: none; 22 | margin: 0; 23 | padding: 0 3rem; 24 | flex-direction: row-reverse; 25 | } 26 | 27 | .share-icons li { 28 | display: inline; 29 | 30 | @include link($fa-color); 31 | .fa { 32 | padding-right: 4px; 33 | font-size: 130%; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/public/front/sass/_functions.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/app/public/front/sass/_functions.scss -------------------------------------------------------------------------------- /src/app/public/front/sass/_header.scss: -------------------------------------------------------------------------------- 1 | .header-container { 2 | .header { 3 | background: $sidebar-color; 4 | position: relative; 5 | top: 0; 6 | width: 100%; 7 | height: $header-height; 8 | transition: all, 0.25s, ease; 9 | .mobile-menu-toggle { 10 | cursor: pointer; 11 | color: $accent-color; 12 | position: absolute; 13 | padding: 5px 15px; 14 | top: 5px; 15 | left: 5px; 16 | transition: all, 0.25s, ease; 17 | &:hover { 18 | color: lighten($accent-color, 15%); 19 | } 20 | } 21 | } 22 | } 23 | 24 | .overlay { 25 | cursor: pointer; 26 | position: fixed; 27 | background: rgba(0, 0, 0, 0.7); 28 | width: 100%; 29 | height: 100%; 30 | z-index: $z-index--overlay; 31 | transition: all, 0.25s, ease; 32 | } 33 | 34 | .openedMenu { 35 | transform: translateX(190px); 36 | } 37 | -------------------------------------------------------------------------------- /src/app/public/front/sass/_help.scss: -------------------------------------------------------------------------------- 1 | .help__wrapper { 2 | } 3 | .help__wrapper--q { 4 | color: $amatsuka-color; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/public/front/sass/_pagination.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | .pagination { 4 | &__term { 5 | display: flex; 6 | justify-content: space-between; 7 | align-items: center; 8 | width: 100%; 9 | padding: 40px 0; 10 | color: $theme-color; 11 | 12 | &--prev { 13 | transform: translate(-5%, -7%); // 目視で 14 | } 15 | 16 | &--next { 17 | transform: translate(5%, -7%); // 目視で 18 | } 19 | } 20 | 21 | &__arrow { 22 | flex: 1; 23 | cursor: pointer; 24 | } 25 | 26 | &__button { 27 | &--left { 28 | margin-right: auto; 29 | @include pagination-button(); 30 | } 31 | 32 | &--right { 33 | margin-left: auto; 34 | @include pagination-button(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/public/front/sass/_shortcut.scss: -------------------------------------------------------------------------------- 1 | .shortcut { 2 | &__container { 3 | z-index: $z-index--shortcut-overlay; 4 | background: rgba(0, 0, 0, 0.8); 5 | } 6 | &__list { 7 | } 8 | &__close { 9 | color: $text-color; 10 | position: absolute; 11 | top: 20px; 12 | right: 15px; 13 | cursor: pointer; 14 | transition: color .3s ease; 15 | &:hover { 16 | color: white; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/app/public/front/sass/_unearth.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | .unearth { 4 | width: 100%; 5 | color: $link-color; 6 | i { 7 | color: $link-color; 8 | } 9 | &__wrapper { 10 | display: flex; 11 | flex-direction: column; 12 | } 13 | &__container { 14 | margin-top: 2rem; 15 | margin-bottom: 2rem; 16 | h3 { 17 | margin-top: 4rem; 18 | margin-bottom: 2rem; 19 | } 20 | .note { 21 | font-size: 32px; 22 | margin: 2rem 0; 23 | } 24 | } 25 | 26 | &__controls { 27 | &.bottom { 28 | border-bottom: 1px solid #464646; 29 | } 30 | &.top { 31 | border-top: 1px solid #464646; 32 | } 33 | div:not(:last-of-type) { 34 | padding-right: 2rem; 35 | } 36 | } 37 | } 38 | .break-line { 39 | display: flex; 40 | flex-flow: wrap; 41 | width: 100%; 42 | } 43 | @media (max-width: 768px) { 44 | .unearth { 45 | &__term { 46 | display: flex; 47 | flex-wrap: wrap; 48 | .term__button { 49 | margin: 0 1rem 2rem 0; 50 | } 51 | } 52 | .selectable-buttons .button { 53 | padding: 0.2rem 1rem; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/app/public/front/sass/app.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | // @import "compass"; 4 | @import 'variables'; 5 | @import 'mixins'; 6 | @import 'functions'; 7 | @import 'commons'; 8 | 9 | @import 'primitive'; 10 | 11 | @import 'ad'; 12 | 13 | @import 'header'; 14 | 15 | @import 'sidebar'; 16 | 17 | @import 'item'; 18 | 19 | @import 'best'; 20 | 21 | @import 'dashboard'; 22 | 23 | @import 'checkbox'; 24 | 25 | @import 'account'; 26 | 27 | @import 'login'; 28 | 29 | @import 'history'; 30 | 31 | @import 'post'; 32 | 33 | @import 'shortcut'; 34 | 35 | @import 'stats'; 36 | 37 | @import 'search'; 38 | 39 | @import 'pagination'; 40 | 41 | @import 'unearth'; 42 | 43 | @import 'loader'; 44 | 45 | @import 'bootstrap'; 46 | 47 | @import 'entire_style'; 48 | 49 | @import 'desktop'; 50 | 51 | @import 'tablet'; 52 | 53 | @import 'smartphone'; 54 | 55 | // https://hayashikejinan.com/webwork/css/913/ 56 | .fa-hatena { 57 | &:before { 58 | content: 'B!'; 59 | font-family: Verdana; 60 | font-weight: bold; 61 | } 62 | } 63 | 64 | button:focus > span { 65 | color: $text-color; 66 | mix-blend-mode: difference; 67 | } 68 | -------------------------------------------------------------------------------- /src/app/routes/api.js: -------------------------------------------------------------------------------- 1 | module.exports = (app) => { 2 | require('./v1/publics')(app); 3 | require('./v1/sessions')(app); 4 | require('./middlewares/session')(app); 5 | require('./v1/dashboard')(app); 6 | require('./v1/transport')(app); 7 | require('./v1/dones')(app); 8 | require('./v1/posts')(app); 9 | require('./v1/stats')(app); 10 | require('./v1/unearth')(app); 11 | require('./v1/favorites')(app); 12 | }; 13 | -------------------------------------------------------------------------------- /src/app/routes/middlewares/index.js: -------------------------------------------------------------------------------- 1 | const auth = require('./auth'); 2 | const logging = require('./logging'); 3 | const parameters = require('./parameters'); 4 | 5 | module.exports = { 6 | auth, 7 | logging, 8 | parameters, 9 | }; 10 | -------------------------------------------------------------------------------- /src/app/routes/middlewares/logging.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const chalk = require('chalk'); 3 | const path = require('path'); 4 | 5 | const { logger } = require(path.resolve('logger')); 6 | 7 | module.exports = { 8 | log(req, res, next) { 9 | if (!_.isEmpty(req.query)) { 10 | logger.info(chalk.bgYellow('req.query ====> ')); 11 | logger.info(req.query); 12 | } 13 | if (!_.isEmpty(req.params)) { 14 | logger.info(chalk.bgBlue('req.params ====> ')); 15 | logger.info(req.params); 16 | } 17 | if (!_.isEmpty(req.body)) { 18 | logger.info(chalk.bgMagenta('req.body ====> ')); 19 | logger.info(req.body); 20 | } 21 | return next(); 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /src/app/routes/middlewares/parameters.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const GetRequestParameter = require(path.resolve( 4 | 'build', 5 | 'app', 6 | 'routes', 7 | 'utils', 8 | 'GetRequestParameter', 9 | )); 10 | 11 | module.exports = { 12 | getParameters(req, res, next) { 13 | req.params = GetRequestParameter.parse(req); 14 | return next(); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/app/routes/middlewares/session.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { logger } = require(path.resolve('logger')); 4 | 5 | module.exports = function (app) { 6 | return app.use('/api/?', (req, res, next) => { 7 | if (typeof req.session.passport.user !== 'undefined') { 8 | return next(); 9 | } 10 | logger.info('(#^.^#) invalid request'); 11 | return res.redirect('/'); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /src/app/routes/routes.js: -------------------------------------------------------------------------------- 1 | module.exports = (app) => { 2 | app.get('/logout', (req, res) => { 3 | if ((req.session != null ? req.session.id : undefined) == null) { 4 | return; 5 | } 6 | req.logout(); 7 | req.session.destroy(); 8 | res.redirect('/'); 9 | }); 10 | 11 | // serve index and view partials 12 | app.get('/', (req, res) => { 13 | res.render('index'); 14 | }); 15 | 16 | app.get('/partials/:name', (req, res) => { 17 | res.render(`partials/${req.params.name}`); 18 | }); 19 | 20 | // redirect all others to the index (HTML5 history) 21 | app.get('*', (req, res) => { 22 | res.render('index'); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /src/app/routes/utils/GetRequestParameter.js: -------------------------------------------------------------------------------- 1 | /* 2 | Expressのreqを一つにまとめる 3 | */ 4 | module.exports = class GetRequestParameter { 5 | static parse(req) { 6 | return Object.assign({}, req.query, req.params, req.body, { user: req.user }); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/app/routes/v1/dashboard.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { auth, logging, parameters } = require(path.resolve( 4 | 'build', 5 | 'app', 6 | 'routes', 7 | 'middlewares', 8 | )); 9 | const { DashboardController } = require(path.resolve('build', 'app', 'controllers')); 10 | 11 | module.exports = (app) => { 12 | // GET 13 | app.get( 14 | '/api/dashboard', 15 | [auth.getUser, parameters.getParameters, logging.log], 16 | DashboardController.find, 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/app/routes/v1/dones.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { auth, logging, parameters } = require(path.resolve('build', 'app', 'routes', 'middlewares')); 4 | const { DoneController } = require(path.resolve('build', 'app', 'controllers')); 5 | 6 | module.exports = (app) => { 7 | app.get('/api/dones/pairs/:postObjectId', [auth.getUser, parameters.getParameters, logging.log], DoneController.findPairs); 8 | 9 | /** 10 | * 自分が投稿した画像を抜いたとき、inboxまたは。Archiveにある画像は消す。 11 | * 他のユーザが投稿した画像を抜いたとき、消す必要はない。 12 | */ 13 | app.post('/api/dones', [auth.getUser, parameters.getParameters, logging.log], DoneController.upsert); 14 | 15 | /** 16 | * 抜いた数の更新 17 | * TODO: PUTで統一すべきでは?動作をURIには含めない 18 | */ 19 | app.put('/api/dones/flucate', [auth.getUser, parameters.getParameters, logging.log], DoneController.flucate); 20 | }; 21 | -------------------------------------------------------------------------------- /src/app/routes/v1/favorites.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { auth, logging, parameters } = require(path.resolve( 4 | 'build', 5 | 'app', 6 | 'routes', 7 | 'middlewares', 8 | )); 9 | const { FavoriteController } = require(path.resolve('build', 'app', 'controllers')); 10 | 11 | module.exports = (app) => { 12 | app.post( 13 | '/api/favorites/:postObjectId', 14 | [auth.getUser, parameters.getParameters, logging.log], 15 | FavoriteController.favorite, 16 | ); 17 | 18 | app.delete( 19 | '/api/favorites/:postObjectId', 20 | [auth.getUser, parameters.getParameters, logging.log], 21 | FavoriteController.unfavorite, 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/app/routes/v1/publics.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { auth, logging, parameters } = require(path.resolve('build', 'app', 'routes', 'middlewares')); 4 | const { ConvertController, PostController, RecommendController } = require(path.resolve('build', 'app', 'controllers')); 5 | 6 | module.exports = (app) => { 7 | app.get('/api/convert/tweet/:tweetId', [parameters.getParameters, logging.log], ConvertController.convertTweetIdToTweetInfo); 8 | 9 | app.get('/api/posts/:type/count/all', [parameters.getParameters, logging.log], PostController.countAll); 10 | 11 | app.post('/api/posts', [auth.checkRegistrable, parameters.getParameters, logging.log], PostController.register); 12 | 13 | app.get('/api/recommends', [parameters.getParameters, logging.log], RecommendController.find); 14 | }; 15 | -------------------------------------------------------------------------------- /src/app/routes/v1/sessions.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { SessionController } = require(path.resolve('build', 'app', 'controllers')); 4 | 5 | module.exports = (app) => { 6 | app.get('/api/sessions', SessionController.get); 7 | }; 8 | -------------------------------------------------------------------------------- /src/app/routes/v1/transport.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { auth, logging, parameters } = require(path.resolve('build', 'app', 'routes', 'middlewares')); 4 | const { TrasnportController } = require(path.resolve('build', 'app', 'controllers')); 5 | 6 | module.exports = (app) => { 7 | app.get('/api/export', [auth.getUser, parameters.getParameters, logging.log], TrasnportController.export); 8 | app.post('/api/import', [auth.getUser, parameters.getParameters, logging.log], TrasnportController.import); 9 | }; 10 | -------------------------------------------------------------------------------- /src/app/routes/v1/unearth.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { auth, logging, parameters } = require(path.resolve( 4 | 'build', 5 | 'app', 6 | 'routes', 7 | 'middlewares', 8 | )); 9 | const { UnearthController } = require(path.resolve('build', 'app', 'controllers')); 10 | 11 | module.exports = (app) => { 12 | /** 13 | * 投稿の種類と期間を指定して返す。 14 | * @param {String} type - 投稿の種類。 inbox|archive|done 15 | * @param {String} amount - 遡る月数。1なら1ヶ月前、12なら1年前。 16 | */ 17 | app.get( 18 | '/api/unearth/:type/:amount?', 19 | [auth.getUser, parameters.getParameters, logging.log], 20 | UnearthController.find, 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/app/server.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { my } = require(path.resolve('build', 'lib', 'my')); 4 | 5 | module.exports = (listener) => { 6 | try { 7 | if (my.toBoolean(process.env.FORCE_HTTP)) throw new Error('Forcing HTTP'); 8 | 9 | const hskey = fs.readFileSync(path.resolve('keys', 'server.key')); 10 | const hscert = fs.readFileSync(path.resolve('keys', 'server.crt')); 11 | 12 | const httpsOptions = { 13 | key: hskey, 14 | cert: hscert, 15 | }; 16 | const { createServer } = require('https'); 17 | return createServer(httpsOptions, listener); 18 | } catch (e) { 19 | const { createServer } = require('http'); 20 | return createServer(listener); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/app/services/done/DonePostPairsService.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const DatabaseProviderFactory = require(path.resolve( 4 | 'build', 5 | 'model', 6 | 'lib', 7 | 'DatabaseProviderFactory', 8 | )); 9 | 10 | module.exports = class DonePostPairsService { 11 | static find(condition) { 12 | // 該ポストと一緒に抜いた履歴、 13 | return DatabaseProviderFactory.createProvider('DoneHistory').findPairsByDonePostObjectID(condition); 14 | } 15 | 16 | static Condition({ user, postObjectId }) { 17 | return { 18 | userObjectId: user._id, // 危険では? auth.getUserして 19 | postObjectId, 20 | }; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/app/services/favorite/FavoriteService.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const DatabaseProviderFactory = require(path.resolve( 4 | 'build', 5 | 'model', 6 | 'lib', 7 | 'DatabaseProviderFactory', 8 | )); 9 | 10 | module.exports = class FavoriteService { 11 | static updateById(condition) { 12 | return DatabaseProviderFactory.createProvider('Done').updateById({ donePost: condition }); 13 | } 14 | 15 | static Condition({ user, postObjectId, favorited }) { 16 | return { 17 | postedBy: user._id, 18 | postObjectId, 19 | favorited, 20 | }; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/app/services/history/DoneHistoryService.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const DatabaseProviderFactory = require(path.resolve( 4 | 'build', 5 | 'model', 6 | 'lib', 7 | 'DatabaseProviderFactory', 8 | )); 9 | 10 | module.exports = class DoneHistoryService { 11 | static find(type, condition) {} 12 | 13 | static save(doneHistory) { 14 | return DatabaseProviderFactory.createProvider('DoneHistory').save({ doneHistory }); 15 | } 16 | 17 | static findLatest(condition) { 18 | return DatabaseProviderFactory.createProvider('DoneHistory').findLatestByDonePostObjectID(condition); 19 | } 20 | 21 | static removeLatest(condition) { 22 | return this.findLatest(condition).then(doneHistory => 23 | this.removeById({ doneHistoryObjectId: doneHistory._id })); 24 | } 25 | 26 | static removeById(condition) { 27 | return DatabaseProviderFactory.createProvider('DoneHistory').removeById(condition); 28 | } 29 | 30 | static DoneHistory(donePost) { 31 | return { 32 | donePost: donePost._id, 33 | images: donePost.images, 34 | postedBy: donePost.postedBy, 35 | originPostedBy: donePost.originPostedBy, 36 | }; 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /src/app/services/index.js: -------------------------------------------------------------------------------- 1 | const DashboardService = require('./post/DashboardService'); 2 | const TrasnportService = require('./transport/TrasnportService'); 3 | const DoneHistoryService = require('./history/DoneHistoryService'); 4 | const DonePostService = require('./done/DonePostService'); 5 | const DonePostPairsService = require('./done/DonePostPairsService'); 6 | const FavoriteService = require('./favorite/FavoriteService'); 7 | const ImageService = require('./post/ImageService'); 8 | const PostService = require('./post/PostService'); 9 | const PostRegisterService = require('./post/PostRegisterService'); 10 | const StatsService = require('./stats/StatsService'); 11 | const UnearthService = require('./unearth/UnearthService'); 12 | 13 | module.exports = { 14 | DashboardService, 15 | TrasnportService, 16 | DoneHistoryService, 17 | DonePostService, 18 | DonePostPairsService, 19 | FavoriteService, 20 | ImageService, 21 | PostService, 22 | PostRegisterService, 23 | StatsService, 24 | UnearthService, 25 | }; 26 | -------------------------------------------------------------------------------- /src/app/services/post/DashboardService.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const path = require('path'); 3 | 4 | const { DashboardRepository } = require(path.resolve( 5 | 'build', 6 | 'model', 7 | 'repository' 8 | )); 9 | 10 | module.exports = class DashboardService { 11 | static find(condition) { 12 | return DashboardRepository.find(condition); 13 | } 14 | 15 | static Condition(params) { 16 | return { 17 | userObjectId: params.user._id, // FIXME: auth.getUserして 18 | limit: params.limit - 0, 19 | skip: params.skip - 0, 20 | sort: params.sort, 21 | hostnames: params.hostnames, 22 | }; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/app/services/stats/StatsService.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { StatisticsRepository } = require(path.resolve( 4 | 'build', 5 | 'model', 6 | 'repository' 7 | )); 8 | 9 | module.exports = class StatsService { 10 | constructor({ userObjectId }) { 11 | this.repository = new StatisticsRepository(userObjectId); 12 | } 13 | 14 | async ranking({ year }) { 15 | return await this.repository.aggregateByYear({ year }); 16 | } 17 | 18 | async heads() { 19 | return await this.repository.heads(); 20 | } 21 | 22 | async total({ year }) { 23 | return await this.repository.countByYear({ year }); 24 | } 25 | 26 | async countByDays({ begin, end }) { 27 | return await this.repository.countByDays({ begin, end }); 28 | } 29 | 30 | async query({ year }) { 31 | return { 32 | ranking: await this.ranking({ year }), 33 | total: await this.total({ year }), 34 | }; 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /src/app/services/transport/TrasnportService.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { TrasnportRepository } = require(path.resolve('build', 'model', 'repository')); 4 | 5 | module.exports = class TrasnportService { 6 | static async export({ user }) { 7 | const record = await TrasnportRepository.export({ user }); 8 | return { record }; 9 | } 10 | static async import({ user, record }) { 11 | return await TrasnportRepository.import({ user, record }); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/app/services/unearth/UnearthService.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { my } = require(path.resolve('build', 'lib', 'my')); 4 | const DatabaseProviderFactory = require(path.resolve( 5 | 'build', 6 | 'model', 7 | 'lib', 8 | 'DatabaseProviderFactory' 9 | )); 10 | 11 | /* 12 | QUESTION: PostServiceなのにDonePostとDoneHistoryが混在している。typeで分岐させるのがすでに失敗だったのでは? 13 | */ 14 | module.exports = class UnearthService { 15 | static find(type, params) { 16 | return DatabaseProviderFactory.createProvider(type).findUnearthByDate( 17 | params 18 | ); 19 | } 20 | 21 | static Condition({ amount, archived, user }) { 22 | const amountMonth = amount || 12; 23 | const userObjectId = user._id; 24 | const day = my.addTimeFormatYMD('months', -amountMonth); 25 | const from = my.startOf(day, 'day', 'YYYY-MM-DD HH:mm:ss'); 26 | const to = my.endOf(day, 'day', 'YYYY-MM-DD HH:mm:ss'); 27 | return { 28 | archived, 29 | userObjectId, 30 | from, 31 | to, 32 | }; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/app/views/googlef7315916b4995617.jade: -------------------------------------------------------------------------------- 1 | google-site-verification: googlef7315916b4995617.html -------------------------------------------------------------------------------- /src/app/views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block body 4 | .h100per(ng-controller='AdminUserCtrl') 5 | .cover-until-authenticated(ng-if='!isReady') 6 | .spinner 7 | 8 | .h100per(ng-if='isReady') 9 | .h100per.login__bg(ng-if='!isAuthenticated') 10 | include ./parts/_login 11 | 12 | span(ng-if='isAuthenticated') 13 | toaster-container 14 | shortcut-help-container 15 | include ./parts/_nav 16 | include ./parts/_sidebar 17 | .site-main(ng-view) 18 | 19 | span(ng-if='!isReady') 20 | .intro-container 21 | .spinner -------------------------------------------------------------------------------- /src/app/views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(ng-app="myApp", ng-cloak, ng-csp) 3 | head 4 | include ./parts/head/_meta 5 | link(rel="manifest", href="manifest.json") 6 | title Kawpaa 7 | base(href='/') 8 | include ./parts/head/_favicon 9 | include ./parts/head/_style 10 | body 11 | block body 12 | 13 | 14 | include ./parts/head/_script -------------------------------------------------------------------------------- /src/app/views/partials/account.jade: -------------------------------------------------------------------------------- 1 | account-container -------------------------------------------------------------------------------- /src/app/views/partials/archive.jade: -------------------------------------------------------------------------------- 1 | //- .col-md-12(ng-controller='PostListCtrl') 2 | //- .section 3 | //- .dashboard 4 | //- include ../parts/_search_box 5 | //- include ../parts/initial_pages/_factory 6 | //- include ../parts/_post_container -------------------------------------------------------------------------------- /src/app/views/partials/done.jade: -------------------------------------------------------------------------------- 1 | //- .col-md-12(ng-controller='PostListCtrl') 2 | //- .section 3 | //- .dashboard 4 | //- include ../parts/_search_box 5 | //- include ../parts/initial_pages/_factory 6 | //- include ../parts/_post_container -------------------------------------------------------------------------------- /src/app/views/partials/explore.jade: -------------------------------------------------------------------------------- 1 | .col-md-12 2 | .section 3 | dashboard 4 | -------------------------------------------------------------------------------- /src/app/views/partials/favorite.jade: -------------------------------------------------------------------------------- 1 | //- .col-md-12(ng-controller='PostListCtrl') 2 | //- .section 3 | //- .dashboard 4 | //- include ../parts/_search_box 5 | //- include ../parts/initial_pages/_factory 6 | //- include ../parts/_post_container -------------------------------------------------------------------------------- /src/app/views/partials/find.jade: -------------------------------------------------------------------------------- 1 | .col-md-12(ng-controller='FindCtrl') 2 | .section 3 | .dashboard 4 | term-pagination(term="term") 5 | include ../parts/initial_pages/_factory 6 | include ../parts/_post_container 7 | span(ng-if="postList.items.length != 0") 8 | item-control-header(page-type='pageType', posts="postList") 9 | zoom-image-viewer(page-type='pageType', posts="postList.items") 10 | term-pagination(term="term") 11 | 12 | -------------------------------------------------------------------------------- /src/app/views/partials/index.jade: -------------------------------------------------------------------------------- 1 | //- .col-md-12(ng-controller='PostListCtrl') 2 | //- .section 3 | //- .dashboard 4 | //- include ../parts/_search_box 5 | //- include ../parts/initial_pages/_factory 6 | //- include ../parts/_post_container -------------------------------------------------------------------------------- /src/app/views/partials/main.jade: -------------------------------------------------------------------------------- 1 | .col-md-12(ng-controller='PostListCtrl') 2 | item-control-header(page-type='pageType', posts="postList") 3 | zoom-image-viewer(page-type='pageType', posts="postList.items") 4 | .section 5 | .dashboard 6 | include ../parts/_search_box 7 | include ../parts/initial_pages/_factory 8 | include ../parts/_post_container 9 | -------------------------------------------------------------------------------- /src/app/views/partials/post.jade: -------------------------------------------------------------------------------- 1 | .post__wrapper 2 | section 3 | .col-md-12.col-xl-9 4 | .post 5 | post-container.post__container.image--center( 6 | type="post.type", 7 | src="post.images.original", 8 | id="post._id", 9 | link="post.siteUrl", 10 | content="post.content" 11 | videos="post.videos") 12 | 13 | .col-md-12.col-xl-3 14 | .relative-info__container 15 | .row 16 | .col-md-2.col-xl-12.post__pagination 17 | a.prev(href="/post/{{::pageType}}/{{post.prev._id}}", ng-if="post.prev != null") 18 | span.link < prev 19 | a.next(href="/post/{{::pageType}}/{{post.next._id}}", ng-if="post.next != null") 20 | span.next__link next > 21 | .col-md-10.col-xl-12 22 | .post__title 23 | a.post__title__anker(href="{{::post.siteUrl}}", target="_blank", rel="noopener nofollow") {{::post.title}} 24 | post-control-container(page-type='::pageType', post-object-id="::postObjectId", post="::post") 25 | .col-md-12 26 | done-post-history(post-object-id="::postObjectId") -------------------------------------------------------------------------------- /src/app/views/partials/stats.jade: -------------------------------------------------------------------------------- 1 | .col-md-12 2 | .section 3 | .stats__container 4 | .stats__section 5 | .stats__header 6 | | Total Activity 7 | stats-counter-container 8 | .stats__section 9 | .stats__header 10 | | Yearly Ranking 11 | stats-ranking-container -------------------------------------------------------------------------------- /src/app/views/partials/stats_ranking.jade: -------------------------------------------------------------------------------- 1 | .col-md-12 2 | .section 3 | .stats__container 4 | .col-sm-12.col-md-12.col-lg-12 5 | stats-year-aggregation-container 6 | -------------------------------------------------------------------------------- /src/app/views/partials/unearth.jade: -------------------------------------------------------------------------------- 1 | section.col-md-12 2 | .row 3 | .unearth__wrapper.flexible.col-sm-12.col-md-10.col-lg-10.col-xl-10.col-xxl-10 4 | .unearth__controls.bottom 5 | unearth-type-changer 6 | .row.unearth__container 7 | div(ng-if="ready") 8 | item-control-header(page-type='pageType', posts="postList") 9 | zoom-image-viewer(page-type='pageType', posts="postList.items") 10 | span(ng-repeat='post in postList.items', ng-if="postList.items.length != 0") 11 | div(ng-if="$index == 0") 12 | .col-md-12.break-line 13 | h3(id="{{post.ago}}") {{post.ago}}年前 14 | div(ng-if="$index != 0") 15 | .col-md-12.break-line(ng-if="postList.items[$index-1].ago != post.ago") 16 | h3(id="{{post.ago}}") {{post.ago}}年前 17 | .item__container.col-xs-6.col-sm-4.col-md-3.col-lg-3.col-xl-1.col-xxl-1 18 | include ../parts/item/item_controls/unearth/_post 19 | .col-md-12.text-center(ng-if="postList.items.length == 0") 20 | p.note 21 | | Empty ... 22 | div(ng-if="!ready") 23 | include ../parts/_puff_loader 24 | .unearth__controls.top 25 | unearth-type-changer 26 | recommend-list.flexible.hidden-sm.col-md-2.col-lg-2.col-xl-2.col-xxl-2.recommends -------------------------------------------------------------------------------- /src/app/views/partials/view.jade: -------------------------------------------------------------------------------- 1 | multi-post-container 2 | -------------------------------------------------------------------------------- /src/app/views/parts/_how_to_save.jade: -------------------------------------------------------------------------------- 1 | h3.description__section 2 | | オカズの追加方法 3 | 4 | .description 5 | | オカズ(リンク、画像、動画)の追加にはChrome Extensionを利用する必要があります。 6 | 7 | .descriptio 8 | ① 以下のURLから最新版のChrome Extensionをダウンロードしてください。 (例) Save.to.Kawpaa.v0.14.0.zip
    9 | p 10 | p 11 | a(href="https://github.com/eiurur/Save-to-Kawpaa/releases", target="_blank", rel="noopener nofollow") 12 | | Releases · Save-to-Kawpaa 13 | i.fa.fa-external-link.zurui-icon-right 14 | 15 | .description 16 | ② つぎに、ダウンロードしたZIPファイルを chrome://extensions にDrag&Dropしてください。
    17 | 18 | img.img-responsive(src='https://github.com/eiurur/Save-to-Kawpaa/raw/master/images/description/drop.jpg') 19 | 20 | 21 | .description 22 | ③ 以下のトークンをChrome Extensionのオプションページに入力してください。
    23 | pre 24 | code 25 | {{user.twitter_token}} 26 | 27 | .description 28 | | オプションページを開くには、Chrome Extensionのアイコンを右クリックしてメニューを開きます。 29 | br 30 | | 「オプション」をクリックするとオプションページが開かれます。 31 | img.img-responsive(src='https://github.com/eiurur/Save-to-Kawpaa/raw/master/images/description/open_option.jpg') 32 | 33 | .description 34 | ④ トークンをコピペしてください。 35 | img.img-responsive(src='https://github.com/eiurur/Save-to-Kawpaa/raw/master/images/description/copy.png') 36 | 37 | .description 38 | これで準備は完了です。気に入ったオカズをストックしてみてください。 39 | 40 | -------------------------------------------------------------------------------- /src/app/views/parts/_loading_fin.jade: -------------------------------------------------------------------------------- 1 | dot-loader.box-loading-padding-vertical(ng-if='tweets.busy') 2 | .text-center(ng-show='tweets.isLast') 終わりです -------------------------------------------------------------------------------- /src/app/views/parts/_loading_init.jade: -------------------------------------------------------------------------------- 1 | dot-loader.user-sidebar__contents--box-loading-init(ng-if='!tweets.items') 2 | .list__status--message.text-center(ng-if='message') {{message}} -------------------------------------------------------------------------------- /src/app/views/parts/_posts_control_container.jade: -------------------------------------------------------------------------------- 1 | .search-condition__container.col-sm-12.col-md-12.col-lg-12(ng-show="isOpenPostControlContainer") 2 | form.form 3 | .form-group 4 | .input-group 5 | span.input-group-addon 6 | i.fa.fa-filter 7 | select(ng-model='filterTypeList.selected', ng-options='l.k for l in filterTypeList.items track by l.k') 8 | .form-group 9 | .input-group 10 | span.input-group-addon 11 | i.fa.fa-sort 12 | select(ng-model='sortTypeList.selected', ng-options='l.k for l in sortTypeList.items track by l.k') -------------------------------------------------------------------------------- /src/app/views/parts/_puff_loader.jade: -------------------------------------------------------------------------------- 1 | img.puff-loader(src="front/images/loaders/puff.svg") -------------------------------------------------------------------------------- /src/app/views/parts/_search_box.jade: -------------------------------------------------------------------------------- 1 | search-condition 2 | 3 | .row 4 | .col-sm-12.col-md-12.col-lg-12 5 | search-box 6 | .search-controls 7 | .common 8 | input#random(type='checkbox', ng-model='random') 9 | label(for='random') Random 10 | 11 | -------------------------------------------------------------------------------- /src/app/views/parts/_share.jade: -------------------------------------------------------------------------------- 1 | ul.share-icons  2 | li 3 | a.clickable(href='http://b.hatena.ne.jp/entry/https://kawpaa.eiurur.xyz', title="bookmark on Hatena Bookmark", target='_blank') 4 | i.fa.fa-hatena 5 | 6 | li 7 | a(href='https://twitter.com/kawpaa', target='_blank', title='Twitter') 8 | i.fa.fa-twitter 9 | 10 | li 11 | a(href='https://github.com/eiurur/Save-to-Kawpaa', target='_blank', title='Save to Kawpaa') 12 | i.fa.fa-github -------------------------------------------------------------------------------- /src/app/views/parts/head/_favicon.jade: -------------------------------------------------------------------------------- 1 | link(rel='apple-touch-icon', sizes='57x57', href='front/images/apple-touch-icon-57x57.png') 2 | link(rel='apple-touch-icon', sizes='60x60', href='front/images/apple-touch-icon-60x60.png') 3 | link(rel='apple-touch-icon', sizes='72x72', href='front/images/apple-touch-icon-72x72.png') 4 | link(rel='apple-touch-icon', sizes='76x76', href='front/images/apple-touch-icon-76x76.png') 5 | link(rel='apple-touch-icon', sizes='114x114', href='front/images/apple-touch-icon-114x114.png') 6 | link(rel='apple-touch-icon', sizes='120x120', href='front/images/apple-touch-icon-120x120.png') 7 | link(rel='apple-touch-icon', sizes='144x144', href='front/images/apple-touch-icon-144x144.png') 8 | link(rel='apple-touch-icon', sizes='152x152', href='front/images/apple-touch-icon-152x152.png') 9 | link(rel='apple-touch-icon', sizes='180x180', href='front/images/apple-touch-icon-180x180.png') 10 | link(rel='icon', type='image/png', href='front/images/favicon-32x32.png', sizes='32x32') 11 | link(rel='icon', type='image/png', href='front/images/android-chrome-192x192.png', sizes='192x192') 12 | link(rel='icon', type='image/png', href='front/images/favicon-96x96.png', sizes='96x96') 13 | link(rel='icon', type='image/png', href='front/images/favicon-16x16.png', sizes='16x16') 14 | meta(name='msapplication-TileColor', content='#da532c') 15 | meta(name='msapplication-TileImage', content='front/images/mstile-144x144.png') 16 | -------------------------------------------------------------------------------- /src/app/views/parts/head/_meta.jade: -------------------------------------------------------------------------------- 1 | meta(charset='utf-8') 2 | meta(property='og:url', content='https://kawpaa.eiurur.xyz') 3 | meta(property='og:site_name', content='Kawpaa') 4 | meta(property='og:title', content='Kawpaa') 5 | meta(property='og:description', content='あとで抜く') 6 | meta(property='og:author', content='eiurur') 7 | meta(property='og:locale', content='ja_JP') 8 | meta(property='og:image', content='https://kawpaa.eiurur.xyz/front/images/ogp.png') 9 | meta(property='twitter:card', content='summary') 10 | meta(http-equiv='X-UA-Compatible', content='IE=edge') 11 | meta(name='viewport', content='width=device-width, initial-scale=1') 12 | meta(name="theme-color", content="#5c5edc") 13 | meta(name='description', content='あとで抜く') 14 | meta(name="keywords", content="射精管理,オナニー,エロ,ero,porn,18禁,masterbation,hentai,danbooru,sankaku,yandere,2chan,twitter,tumblr,pixiv") 15 | meta(name="google-site-verification", content="UdWpI2XMBNvCb2SvZMDBjHREfrlwWNEBLZX5feyTSiI") 16 | -------------------------------------------------------------------------------- /src/app/views/parts/head/_script.jade: -------------------------------------------------------------------------------- 1 | if process.env.NODE_ENV == "production" 2 | script(src="front/js/vendors/lib.min.js") 3 | script(src="front/js/vendors/angular-lib.min.js") 4 | script(src="front/js/main.min.js") 5 | script(src="https://spope.github.io/MiniMasonry.js/minimasonry.min.js") 6 | script. 7 | (function(i,s,o,g,r,a,m){i["GoogleAnalyticsObject"]=r;i[r]=i[r]||function(){ 8 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 9 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 10 | })(window,document,"script","//www.google-analytics.com/analytics.js","ga"); 11 | ga("create", "UA-42893827-19", "auto"); 12 | else 13 | script(src="front/js/vendors/lib.js") 14 | script(src="front/js/vendors/angular-lib.js") 15 | script(src="front/js/main.js") 16 | script(src="https://spope.github.io/MiniMasonry.js/minimasonry.min.js") -------------------------------------------------------------------------------- /src/app/views/parts/head/_style.jade: -------------------------------------------------------------------------------- 1 | if process.env.NODE_ENV == "production" 2 | link(rel="stylesheet", href="front/css/vendors/lib.min.css") 3 | link(rel="stylesheet", href="front/css/app.min.css") 4 | else 5 | link(rel="stylesheet", href="front/css/vendors/lib.css") 6 | link(rel="stylesheet", href="front/css/app.css") -------------------------------------------------------------------------------- /src/app/views/parts/initial_pages/_archive.jade: -------------------------------------------------------------------------------- 1 | span(ng-if="postList.isLast && postList.items.length == 0") 2 | h2 3 | | Archive is empty ... 4 | br 5 | -------------------------------------------------------------------------------- /src/app/views/parts/initial_pages/_done.jade: -------------------------------------------------------------------------------- 1 | span(ng-if="postList.isLast && postList.items.length == 0") 2 | h2 3 | | Done is empty ... 4 | br 5 | -------------------------------------------------------------------------------- /src/app/views/parts/initial_pages/_factory.jade: -------------------------------------------------------------------------------- 1 | div(ng-switch="pageType") 2 | div(ng-switch-when="post") 3 | span 4 | div(ng-switch-when="archive") 5 | include ./_archive 6 | div(ng-switch-when="done") 7 | include ./_done 8 | div(ng-switch-when="favorite") 9 | include ./_favorite 10 | div(ng-switch-when="find") 11 | include ./_find 12 | div(ng-switch-when="history") 13 | include ./_history 14 | div(ng-switch-when="unearth") 15 | include ./_unearth 16 | div(ng-switch-default) 17 | include ./_inbox 18 | -------------------------------------------------------------------------------- /src/app/views/parts/initial_pages/_favorite.jade: -------------------------------------------------------------------------------- 1 | span(ng-if="postList.isLast && postList.items.length == 0") 2 | h2 3 | | Favorite is empty ... 4 | br 5 | -------------------------------------------------------------------------------- /src/app/views/parts/initial_pages/_find.jade: -------------------------------------------------------------------------------- 1 | .dashboard__message--empty(ng-if="postList.isLast && postList.items.length == 0") 2 | | There are no contents 3 | -------------------------------------------------------------------------------- /src/app/views/parts/initial_pages/_history.jade: -------------------------------------------------------------------------------- 1 | span(ng-if="postList.isLast && postList.items.length == 0") 2 | h2 3 | | Done is empty ... 4 | br 5 | -------------------------------------------------------------------------------- /src/app/views/parts/initial_pages/_inbox.jade: -------------------------------------------------------------------------------- 1 | span(ng-if="postList.isLast && postList.items.length == 0") 2 | h2 3 | | Inbox is empty ... 4 | br 5 | br 6 | include ../_how_to_save -------------------------------------------------------------------------------- /src/app/views/parts/initial_pages/_unearth.jade: -------------------------------------------------------------------------------- 1 | .col-md-12(ng-if="!hasAnyPost && ready") 2 | h1 No data X( 3 | p 4 | | Unearthは過去の inbox, archive, done を表示する機能です。 -------------------------------------------------------------------------------- /src/app/views/parts/item/_item_control.jade: -------------------------------------------------------------------------------- 1 | div(ng-switch="pageType") 2 | div(ng-switch-when="post") 3 | include ./item_controls/post/_inbox 4 | div(ng-switch-when="archive") 5 | include ./item_controls/post/_archive 6 | div(ng-switch-when="done") 7 | include ./item_controls/post/_done 8 | div(ng-switch-when="favorite") 9 | include ./item_controls/post/_done 10 | div(ng-switch-when="find") 11 | include ./item_controls/post/_find 12 | div(ng-switch-default) 13 | include ./item_controls/post/_inbox 14 | -------------------------------------------------------------------------------- /src/app/views/parts/item/_item_image.jade: -------------------------------------------------------------------------------- 1 | img.item__image( 2 | ng-src="data/thumbnails/{{::post.fromImgSrc}}", 3 | to-img-src='data/thumbnails/{{::post.toImgSrc}}', 4 | img-src="data/images/{{::post.images.original}}", 5 | id="{{::post._id}}", 6 | pipe-low-to-high-image, open-zoom-image-viewer) 7 | -------------------------------------------------------------------------------- /src/app/views/parts/item/_item_title.jade: -------------------------------------------------------------------------------- 1 | .item__title(ng-class="{'item__title-image': post.type == 'image'}") 2 | a.item__title-anker(title="{{::post.title}}", href="{{::post.siteUrl}}", target="_blank", rel="noopener nofollow") {{::post.title}} -------------------------------------------------------------------------------- /src/app/views/parts/item/item_controls/post/_archive.jade: -------------------------------------------------------------------------------- 1 | .item__control 2 | .item__control--contents 3 | .item__control--contents--icons 4 | i.fa__size--sm.clickable.fa.fa-tint.icon-tint(post-object-id='{{::post._id}}', posts="::postList", done, title='抜いた') 5 | i.fa__size--sm.clickable.fa.fa-inbox.icon-inbox(post-object-id='{{::post._id}}', posts="::postList", revert-inbox, title='Inboxに戻す') 6 | i.fa__size--sm.clickable.fa.fa-trash.icon-trash(post-object-id='{{::post._id}}', posts="::postList", remove, title='削除する') 7 | a 8 | i.fa__size--sm.clickable.fa.fa-external-link(page-type='{{::pageType}}', post-object-id='{{::post._id}}', open, title='新しいウィンドウで見る') 9 | -------------------------------------------------------------------------------- /src/app/views/parts/item/item_controls/post/_done.jade: -------------------------------------------------------------------------------- 1 | .item__control 2 | .item__control--contents 3 | .item__control--contents--icons 4 | {{post.numDone}} 5 | i.fa__size--sm.clickable.fa.fa-plus.icon-plus(post-object-id='{{::post._id}}', num-done='post.numDone', type='increase', flucate, title='さらに抜いた') 6 | i.fa__size--sm.clickable.fa.fa-minus.icon-minus(post-object-id='{{::post._id}}', num-done='post.numDone', type='decrease', flucate, title='やっぱり抜いてない') 7 | i.fa__size--sm.clickable.fa.fa-heart.icon-heart(post-object-id='{{::post._id}}', posts="::postList", favorited='post.favorited', favorite, title='お気に入りに追加する') 8 | i.fa__size--sm.clickable.fa.fa-trash.icon-trash(post-object-id='{{::post._id}}', posts="::postList",type='done', remove, title='削除する') 9 | // aタグがないとtextとcontrolsの間の余白がなくなり、レイアウトが崩れる。 10 | a 11 | i.fa__size--sm.clickable.fa.fa-external-link(page-type='{{::pageType}}', post-object-id='{{::post._id}}', open, title='新しいウィンドウで見る') 12 | -------------------------------------------------------------------------------- /src/app/views/parts/item/item_controls/post/_doneHistory.jade: -------------------------------------------------------------------------------- 1 | .item__control 2 | .item__control--contents 3 | .item__control--contents--icons 4 | {{post.numDone}} 5 | i.fa__size--sm.clickable.fa.fa-plus.icon-plus(post-object-id='{{::post._id}}', num-done='post.numDone', type='increase', flucate, title='さらに抜いた') 6 | i.fa__size--sm.clickable.fa.fa-minus.icon-minus(post-object-id='{{::post._id}}', num-done='post.numDone', type='decrease', flucate, title='やっぱり抜いてない') 7 | i.fa__size--sm.clickable.fa.fa-heart.icon-heart(post-object-id='{{::post._id}}', posts="::postList", favorited='post.favorited', favorite, title='お気に入りに追加する') 8 | i.fa__size--sm.clickable.fa.fa-trash.icon-trash(post-object-id='{{::post._id}}', posts="::postList",type='done', remove, title='削除する') 9 | // aタグがないとtextとcontrolsの間の余白がなくなり、レイアウトが崩れる。 10 | a 11 | i.fa__size--sm.clickable.fa.fa-external-link(page-type='done', post-object-id='{{::post._id}}', open, title='新しいウィンドウで見る') 12 | -------------------------------------------------------------------------------- /src/app/views/parts/item/item_controls/post/_find.jade: -------------------------------------------------------------------------------- 1 | .item__control 2 | .item__control--contents 3 | .item__control--contents--icons.flex-center 4 | i.fa__size--sm.clickable.fa.fa-inbox.icon-inbox(post-object-id='{{::post._id}}', posts="postList",add-inbox, title='Inboxに追加') -------------------------------------------------------------------------------- /src/app/views/parts/item/item_controls/post/_inbox.jade: -------------------------------------------------------------------------------- 1 | .item__control 2 | .item__control--contents 3 | .item__control--contents--icons 4 | i.fa__size--sm.clickable.fa.fa-tint.icon-tint(post-object-id='{{::post._id}}', posts="::postList", done, title='抜いた') 5 | i.fa__size--sm.clickable.fa.fa-check.icon-check(post-object-id='{{::post._id}}', posts="::postList", archive, title='アーカイブに保存する') 6 | i.fa__size--sm.clickable.fa.fa-trash.icon-trash(post-object-id='{{::post._id}}', posts="::postList", remove, title='削除する') 7 | a 8 | i.fa__size--sm.clickable.fa.fa-external-link(page-type='{{::pageType}}', post-object-id='{{::post._id}}', open, title='新しいウィンドウで見る') 9 | -------------------------------------------------------------------------------- /src/app/views/parts/item/item_controls/unearth/_archive.jade: -------------------------------------------------------------------------------- 1 | .item__control 2 | .item__control--contents 3 | .item__control--contents--icons 4 | i.fa__size--sm.clickable.fa.fa-tint.icon-tint(post-object-id='{{::post._id}}', posts="postList", done, title='抜いた') 5 | i.fa__size--sm.clickable.fa.fa-inbox.icon-inbox(post-object-id='{{::post._id}}', posts="postList",revert-inbox, title='Inboxに戻す') 6 | i.fa__size--sm.clickable.fa.fa-trash.icon-trash(post-object-id='{{::post._id}}', posts="postList",remove, title='削除する') 7 | a 8 | i.fa__size--sm.clickable.fa.fa-external-link(page-type='{{::post.category}}', post-object-id='{{::post._id}}', open, title='新しいウィンドウで見る') 9 | -------------------------------------------------------------------------------- /src/app/views/parts/item/item_controls/unearth/_done.jade: -------------------------------------------------------------------------------- 1 | .item__control 2 | .item__control--contents 3 | .item__control--contents--icons 4 | {{post.numDone}} 5 | i.fa__size--sm.clickable.fa.fa-plus.icon-plus(post-object-id='{{::post._id}}', num-done='post.numDone', type='increase', flucate, title='さらに抜いた') 6 | i.fa__size--sm.clickable.fa.fa-minus.icon-minus(post-object-id='{{::post._id}}', num-done='post.numDone', type='decrease', flucate, title='やっぱり抜いてない') 7 | i.fa__size--sm.clickable.fa.fa-heart.icon-heart(post-object-id='{{::post._id}}', posts="postList", favorited='post.favorited', favorite, title='お気に入りに追加する') 8 | i.fa__size--sm.clickable.fa.fa-trash.icon-trash(post-object-id='{{::post._id}}', posts="postList",type='done', remove, title='削除する') 9 | // aタグがないとtextとcontrolsの間の余白がなくなり、レイアウトが崩れる。 10 | a 11 | i.fa__size--sm.clickable.fa.fa-external-link(page-type='{{::post.category}}', post-object-id='{{::post._id}}', open, title='新しいウィンドウで見る') 12 | -------------------------------------------------------------------------------- /src/app/views/parts/item/item_controls/unearth/_doneHistory.jade: -------------------------------------------------------------------------------- 1 | .item__control 2 | .item__control--contents 3 | .item__control--contents--icons 4 | {{post.numDone}} 5 | i.fa__size--sm.clickable.fa.fa-plus.icon-plus(post-object-id='{{::post._id}}', num-done='post.numDone', type='increase', flucate, title='さらに抜いた') 6 | i.fa__size--sm.clickable.fa.fa-minus.icon-minus(post-object-id='{{::post._id}}', num-done='post.numDone', type='decrease', flucate, title='やっぱり抜いてない') 7 | i.fa__size--sm.clickable.fa.fa-heart.icon-heart(post-object-id='{{::post._id}}', posts="postList", favorited='post.favorited', favorite, title='お気に入りに追加する') 8 | i.fa__size--sm.clickable.fa.fa-trash.icon-trash(post-object-id='{{::post._id}}', posts="postList",type='done', remove, title='削除する') 9 | // aタグがないとtextとcontrolsの間の余白がなくなり、レイアウトが崩れる。 10 | a 11 | i.fa__size--sm.clickable.fa.fa-external-link(page-type='done', post-object-id='{{::post._id}}', open, title='新しいウィンドウで見る') 12 | -------------------------------------------------------------------------------- /src/app/views/parts/item/item_controls/unearth/_inbox.jade: -------------------------------------------------------------------------------- 1 | .item__control 2 | .item__control--contents 3 | .item__control--contents--icons 4 | i.fa__size--sm.clickable.fa.fa-tint.icon-tint(post-object-id='{{::post._id}}', posts="postList", done, title='抜いた') 5 | i.fa__size--sm.clickable.fa.fa-check.icon-check(post-object-id='{{::post._id}}', posts="postList", archive, title='アーカイブに保存する') 6 | i.fa__size--sm.clickable.fa.fa-trash.icon-trash(post-object-id='{{::post._id}}', posts="postList", remove, title='削除する') 7 | a 8 | i.fa__size--sm.clickable.fa.fa-external-link(page-type='{{::post.category}}', post-object-id='{{::post._id}}', open, title='新しいウィンドウで見る') 9 | -------------------------------------------------------------------------------- /src/app/views/parts/item/item_controls/unearth/_post.jade: -------------------------------------------------------------------------------- 1 | .item 2 | .item__content(type="{{::post.type}}", post-object-id="{{::post._id}}", item-border, multi-select) 3 | img.item__image( 4 | ng-src='data/thumbnails/{{::post.fromImgSrc}}', 5 | to-img-src='data/thumbnails/{{::post.middleImgSrc}}', 6 | img-src="data/images/{{::post.images.original}}", 7 | id="{{::post._id}}", 8 | pipe-low-to-high-image, open-zoom-image-viewer) 9 | .item__info 10 | include ../../_item_title 11 | div(ng-switch="post.category") 12 | div(ng-switch-when="post") 13 | include ./_inbox 14 | div(ng-switch-when="archive") 15 | include ./_archive 16 | div(ng-switch-when="done") 17 | include ./_done 18 | div(ng-switch-when="favorite") 19 | include ./_done 20 | div(ng-switch-default) 21 | include ./_inbox 22 | -------------------------------------------------------------------------------- /src/domains/data/files/KawpaaImageFile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra'); 2 | const path = require('path'); 3 | 4 | const { APP_FQDN, DIRECTORIES } = require(path.resolve('build', 'lib', 'constants')); 5 | 6 | module.exports = class KawpaaImageFile { 7 | constructor(name, size) { 8 | this.imageDir = size === 'thumbnail' ? DIRECTORIES.THUMBNAILS_TO : DIRECTORIES.IMAGES_TO; 9 | this.name = name; 10 | this.path = `${this.imageDir}/${this.name}`; 11 | return this; 12 | } 13 | 14 | exist() { 15 | try { 16 | fs.accessSync(this.path); 17 | 18 | // ?origのファイルをaccessだと読み込める。readFileでも読み込める。サイズで判断するしかない。 19 | // const stat = fs.statSync(this.path) 20 | // if (stat.size < 1) return false; 21 | 22 | return true; 23 | } catch (e) { 24 | return false; 25 | } 26 | } 27 | 28 | async toBase64() { 29 | const data = await fs.readFile(this.path); 30 | const base64 = data.toString('base64'); 31 | return base64; 32 | } 33 | 34 | toUrl(size) { 35 | const a = process.env.NODE_ENV === 'production' ? `${APP_FQDN}/data` : 'https://127.0.0.1:9021/data'; 36 | const url = `${a}/${size}/${this.name}`; 37 | return url; 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /src/domains/download/images/KawpaaImageDownloader.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const ImageDownloader = require(path.resolve('build', 'lib', 'ImageDownloader')); 4 | const KawpaaHttpProxy = require(path.resolve('build', 'domains', 'proxy', 'KawpaaHttpProxy')); 5 | 6 | module.exports = class KawpaaImageDownloader { 7 | constructor({ hostName, siteUrl, url, filepath }) { 8 | this.hostName = hostName; 9 | this.siteUrl = siteUrl; 10 | this.url = url; 11 | this.filepath = filepath; 12 | } 13 | 14 | /** 15 | * HttpProxyを介してfetchする 16 | * @param {Object} process - 17 | * @return {Promise} byte - 18 | */ 19 | async fetch({ process }) { 20 | const proxy = new KawpaaHttpProxy(); 21 | proxy.setUrl(this.hostName, this.url); 22 | proxy.setHeaders(this.hostName, this.siteUrl); 23 | const byte = await proxy.execute(process); 24 | return byte; 25 | } 26 | 27 | async download() { 28 | const downloader = new ImageDownloader(this.url, this.filepath); 29 | return await this.fetch({ process: downloader }); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/domains/download/images/KawpaaThumbnailGenerator.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const ThumbnailGenerator = require(path.resolve( 4 | 'build', 5 | 'lib', 6 | 'ThumbnailGenerator' 7 | )); 8 | const { logger } = require(path.resolve('logger')); 9 | module.exports = class KawpaaThumbnailGenerator extends ThumbnailGenerator { 10 | static async generate({ filename }) { 11 | try { 12 | const thumbnailFileNameList = await super.generate(filename, [ 13 | 30, 14 | 120, 15 | 240, 16 | 480, 17 | ]); 18 | const result = { 19 | original: filename, 20 | tiny: thumbnailFileNameList[0].filename, 21 | mini: thumbnailFileNameList[1].filename, 22 | small: thumbnailFileNameList[2].filename, 23 | medium: thumbnailFileNameList[3].filename, 24 | }; 25 | return result; 26 | } catch (err) { 27 | logger.info('KawpaaThumbnailGenerator.error => '); 28 | logger.info(filename); 29 | logger.info(err); 30 | return []; 31 | } 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/domains/download/video/KawpaaVideoDownloader.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const VideoDownloader = require(path.resolve('build', 'lib', 'VideoDownloader')); 4 | const KawpaaHttpProxy = require(path.resolve('build', 'domains', 'proxy', 'KawpaaHttpProxy')); 5 | 6 | module.exports = class KawpaaVideoDownloader { 7 | constructor({ hostName, siteUrl, url, filepath }) { 8 | this.hostName = hostName; 9 | this.siteUrl = siteUrl; 10 | this.url = url; 11 | this.filepath = filepath; 12 | } 13 | 14 | /** 15 | * HttpProxyを介してfetchする 16 | * @param {Object} process - 17 | * @return {Promise} byte - 18 | */ 19 | async fetch({ process }) { 20 | const proxy = new KawpaaHttpProxy(); 21 | proxy.setUrl(this.hostName, this.url); 22 | proxy.setHeaders(this.hostName, this.siteUrl); 23 | const byte = await proxy.execute(process); 24 | return byte; 25 | } 26 | 27 | /** 28 | * 29 | */ 30 | async download() { 31 | const downloader = new VideoDownloader(this.url, this.filepath); 32 | return await this.fetch({ process: downloader }); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/domains/entities/Archive.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/domains/entities/Archive.js -------------------------------------------------------------------------------- /src/domains/entities/Done.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/domains/entities/Done.js -------------------------------------------------------------------------------- /src/domains/entities/Image.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/domains/entities/Image.js -------------------------------------------------------------------------------- /src/domains/entities/Inbox.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiurur/Kawpaa/a51576ce438eedd3a58f0245ad48275475e746e9/src/domains/entities/Inbox.js -------------------------------------------------------------------------------- /src/domains/entities/index.js: -------------------------------------------------------------------------------- 1 | const Archive = require('./Archive'); 2 | const Done = require('./Done'); 3 | const Image = require('./Image'); 4 | const Inbox = require('./Inbox'); 5 | 6 | const ImageFile = require('./ImageFile'); 7 | const VideoFile = require('./VideoFile'); 8 | 9 | module.exports = { 10 | Archive, 11 | Done, 12 | Image, 13 | Inbox, 14 | ImageFile, 15 | VideoFile, 16 | }; 17 | -------------------------------------------------------------------------------- /src/domains/exceptions/httpException.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | require('dotenv').config(); 3 | 4 | class KawpaaException extends Error { 5 | constructor(message) { 6 | super(message); 7 | } 8 | } 9 | 10 | class KawpaaHttpException extends KawpaaException { 11 | constructor(message, statusCode = 500) { 12 | super(message); 13 | this.statusCode = statusCode; 14 | } 15 | } 16 | 17 | exports.httpException = { 18 | BadRequest: () => new KawpaaHttpException('Bad Request', 400), 19 | Forbidden: () => new KawpaaHttpException('Forbidden', 403), 20 | NotFound: () => new KawpaaHttpException('Not Found', 404), 21 | FailedPixivAuthentication: () => new KawpaaHttpException('Failed Pixiv Authentication', 400), 22 | FailedSankakuComplexAuthentication: () => new KawpaaHttpException('Failed SankakuComplex Authentication', 400), 23 | FilseSizeOverLimit: () => new KawpaaHttpException(`Savable file size is up to ${process.env.MAX_UPLOADABLE_FILESIZE_MB}MB`, 480), 24 | UploadingRestrictedyMode: () => new KawpaaHttpException('Uploading is currently being restricted.', 580), 25 | }; 26 | -------------------------------------------------------------------------------- /src/lib/FileSizeScaler.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const path = require('path'); 3 | 4 | const { logger } = require(path.resolve('logger')); 5 | /** 6 | * ファイルの量り 7 | */ 8 | module.exports = class FileSizeScaler { 9 | constructor() {} 10 | 11 | setUrl(url) { 12 | this.url = url; 13 | return this; 14 | } 15 | 16 | setOptions(options = {}) { 17 | this.options = options; 18 | return this; 19 | } 20 | 21 | execute() { 22 | return new Promise((resolve, reject) => { 23 | logger.info('FileSizeScaler'); 24 | logger.info('url: ', this.url); 25 | logger.info('options', this.options); 26 | axios 27 | .head(this.url, this.options) 28 | .then((response) => resolve(response.headers['content-length'])) 29 | .catch((err) => reject(err)); 30 | }); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/lib/ImageDownloader.js: -------------------------------------------------------------------------------- 1 | const fsp = require('fs-extra'); 2 | const request = require('request'); 3 | const path = require('path'); 4 | 5 | module.exports = class ImageDownloader { 6 | constructor(url, filepath) { 7 | this.url = url; 8 | this.filepath = filepath; 9 | } 10 | 11 | setUrl(url) { 12 | this.url = url; 13 | return this; 14 | } 15 | 16 | setOptions(options) { 17 | this.options = options; 18 | return this; 19 | } 20 | 21 | execute() { 22 | return new Promise(async (resolve, reject) => { 23 | await fsp.mkdirs(path.dirname(this.filepath)); 24 | const ws = fsp.createWriteStream(this.filepath); 25 | request(this.url, this.options).pipe(ws); 26 | ws.on('finish', () => resolve(this.url)); 27 | ws.on('error', (err) => { 28 | ws.end(); 29 | return reject(err); 30 | }); 31 | }); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/lib/ImageManager.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const ImageMeasure = require(path.resolve('build', 'lib', 'utils', 'ImageMeasure')); 4 | 5 | module.exports = class ImageManager { 6 | constructor(imageFile) { 7 | this.imageFile = imageFile; 8 | } 9 | 10 | async getImageInfo() { 11 | const imageSize = await ImageMeasure.getImageSize(this.imageFile.original); 12 | return { 13 | originImageWidth: imageSize.width, 14 | originImageHeight: imageSize.height, 15 | // originFileName: this.imageFile.original, 16 | // thumbnailFileName: this.imageFile.thumbnail, 17 | }; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/lib/OpenGraphMerger.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | const og = require('open-graph'); 3 | const path = require('path'); 4 | 5 | const { CONTENT_TYPES } = require(path.resolve('build', 'lib', 'constants')); 6 | 7 | module.exports = class OpenGraphMerger { 8 | static ogp(url) { 9 | return util.promisify(og)(url); 10 | } 11 | 12 | static async merge(obj) { 13 | // 足りないデータがあり、typeがlinkなら 14 | const isFulfilledData = obj.url || obj.siteImage || obj.description; 15 | if ( 16 | [CONTENT_TYPES.IMAGE, CONTENT_TYPES.VIDEO].includes(obj.type) 17 | || isFulfilledData 18 | ) { 19 | return obj; 20 | } 21 | 22 | const url = obj.siteUrl; 23 | let meta = null; 24 | try { 25 | meta = await this.ogp(url); 26 | } catch (e) { 27 | } 28 | if (!meta) { 29 | return obj; 30 | } 31 | 32 | const newObj = { 33 | ...obj, 34 | url: meta.image.url, 35 | siteImage: meta.image.url, 36 | description: meta.description, 37 | }; 38 | 39 | return newObj; 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /src/lib/ThumbnailGenerator.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const Samune = require('samune'); 3 | 4 | const { logger } = require(path.resolve('logger')); 5 | 6 | const { DIRECTORIES } = require(path.resolve('build', 'lib', 'constants')); 7 | 8 | module.exports = class ThumbnailGenerator { 9 | static async generate(filename, sizeList) { 10 | try { 11 | const opts = { 12 | url: `${DIRECTORIES.IMAGES_TO}/${filename}`, 13 | dstDir: DIRECTORIES.THUMBNAILS_TO, 14 | resizeOptions: { quality: 75 }, 15 | }; 16 | const samune = new Samune(opts); 17 | const thumbnailFileNameList = await samune.generate(sizeList); 18 | return thumbnailFileNameList; 19 | } catch (err) { 20 | console.log(`${DIRECTORIES.IMAGES_TO}/${filename}`); 21 | logger.info('ThumbnailGenerator.error =>'); 22 | logger.info(filename); 23 | logger.info(err); 24 | return []; 25 | } 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /src/lib/VideoDownloader.js: -------------------------------------------------------------------------------- 1 | const fsp = require('fs-extra'); 2 | const request = require('request'); 3 | const path = require('path'); 4 | const { logger } = require(path.resolve('logger')); 5 | 6 | module.exports = class VideoDownloader { 7 | constructor(url, filepath) { 8 | this.url = url; 9 | this.filepath = filepath; 10 | } 11 | 12 | setUrl(url) { 13 | this.url = url; 14 | return this; 15 | } 16 | 17 | setOptions(options) { 18 | this.options = options; 19 | return this; 20 | } 21 | 22 | getEncodingOptions() { 23 | return { 24 | videoCodec: 'libvpx', // WebM-VP8 25 | // crf = 定率係数。x264のとき、量子化スケールの範囲は0~51。0は無損失。23がデフォルト。51が最悪。 26 | // vpxの範囲は4~63。 27 | options: ['-qmin 0', '-qmax 50', '-crf 5'], 28 | audioCodec: 'libvorbis', 29 | }; 30 | } 31 | 32 | execute() { 33 | return new Promise(async (resolve, reject) => { 34 | await fsp.mkdirs(path.dirname(this.filepath)); 35 | const ws = fsp.createWriteStream(this.filepath); 36 | request(this.url, this.options).pipe(ws); 37 | ws.on('finish', () => resolve(this.url)); 38 | ws.on('error', (err) => { 39 | ws.end(); 40 | return reject(err); 41 | }); 42 | }); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /src/lib/constants/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | /** 4 | * デフォルトのリンクの画像のパス 5 | */ 6 | exports.DEFAULT_FILES = { 7 | DEFAULT_LINK_ICON: path.resolve('build', 'app', 'public', 'front', 'images', 'thumbnails', 'default_link_icon.png'), 8 | }; 9 | 10 | exports.DEFAULT_IMAGE_EXTENSION = '.jpg'; 11 | exports.APP_FQDN = 'https://kawpaa.eiurur.xyz'; 12 | 13 | /** 14 | * 画像の保存先 15 | */ 16 | exports.DIRECTORIES = { 17 | IMAGES_TO: path.resolve('data', 'images'), 18 | THUMBNAILS_TO: path.resolve('data', 'thumbnails'), 19 | VIDEOS_TO: path.resolve('data', 'videos'), 20 | }; 21 | 22 | /** 23 | * 24 | */ 25 | exports.EXCEPTION_SITE_URLS = ['www.youtube.com/watch?v=', 'pornhub.com', 'komiflo.com/comics/', 'ecchi.iwara.tv']; 26 | 27 | /** 28 | * 29 | */ 30 | exports.SPECIAL_HOSTNAME = { 31 | DANBOORU_HOSTNAME: 'danbooru.donmai.us', 32 | DEVIANTART_HOSTNAME: 'deviantart.com', 33 | GELBOORU_HOSTNAME: 'gelbooru.com', 34 | PIXIV_HOSTNAME: 'pixiv.net', 35 | KOMIFLO_HOSTNAME: 'komiflo.com', 36 | KONACHAN_HOSTNAME: 'konachan.com', 37 | SANKAKUCOMPLEX_HOSTNAME: 'sankakucomplex.com', 38 | }; 39 | 40 | /** 41 | * 42 | */ 43 | exports.CONTENT_TYPES = { 44 | IMAGE: 'image', 45 | LINK: 'link', 46 | TEXT: 'text', 47 | VIDEO: 'video', 48 | }; 49 | -------------------------------------------------------------------------------- /src/lib/crawlers/FindCategoryPost.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { CONTENT_TYPES } = require(path.resolve('build', 'lib', 'constants')); 4 | 5 | module.exports = class FindCategoryPost { 6 | constructor({ name, term, date, illust, images }) { 7 | this.content = null; 8 | this.type = CONTENT_TYPES.IMAGE; 9 | this.url = illust.images.large || illust.images.medium; 10 | this.hostName = this.hostname; 11 | this.title = illust.title; 12 | this.siteUrl = illust.source; 13 | this.siteName = name; 14 | this.description = term; 15 | this.favicon = null; 16 | this.originImageWidth = illust.width; 17 | this.originImageHeight = illust.height; 18 | this.postedBy = null; 19 | this.isPrivate = false; 20 | this.isArchive = false; 21 | this.createdAt = date; 22 | this.images = images._id; // CAUTION: ImageSchemaだからimagesとsがつく。imageのリストではない 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/lib/crawlers/FindImageCollection.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const Sosyaku = require('sosyaku'); 3 | 4 | const KawpaaThumbnailGenerator = require(path.resolve( 5 | 'build', 6 | 'domains', 7 | 'download', 8 | 'images', 9 | 'KawpaaThumbnailGenerator', 10 | )); 11 | 12 | module.exports = class FindImageCollection { 13 | constructor({ illusts }) { 14 | this.illusts = illusts; 15 | this.thumnbnails = []; 16 | } 17 | 18 | getThumbnails() { 19 | return this.thumnbnails; 20 | } 21 | 22 | /** 23 | * 保存対象外の画像を除外する 24 | * Hack:: メモリ消費量が激しいのでGifファイルは除外する。imagemagickの知見がたまったら要修正 25 | * @param {Array} excludedExtensions - 除外する拡張子 26 | * @return {Array} 指定の拡張子を持つ画像を除外した配列 27 | */ 28 | rejectNonTargetImage(excludedExtensions = ['.gif']) { 29 | return this.illusts.filter(illust => excludedExtensions.includes(illust.filename) === -1); 30 | } 31 | 32 | /** 33 | * 一定の間隔を空けてサムネイルを作成する 34 | */ 35 | async generateThumnail() { 36 | const options = { 37 | limit: 3, 38 | interval_s: 5, 39 | dataList: this.illusts, 40 | task(illust) { 41 | return KawpaaThumbnailGenerator.generate({ filename: illust.filename }); 42 | }, 43 | }; 44 | const sosyaku = new Sosyaku(options); 45 | this.thumnbnails = await sosyaku.bite(); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /src/lib/crawlers/anime/AmatsukaImageCrawler.js: -------------------------------------------------------------------------------- 1 | const Mizu = require('mizu'); 2 | const path = require('path'); 3 | 4 | const ImageCrawler = require(path.join('..', 'ImageCrawler')); 5 | const CRAWLING_SITE_NAME = 'amatsuka'; 6 | 7 | module.exports = class AmatsukaImageCrawler extends ImageCrawler { 8 | constructor(term, date, sortType) { 9 | super({ name: CRAWLING_SITE_NAME, term, date }); 10 | this.sortType = sortType; 11 | this.setCrawler(); 12 | } 13 | 14 | setCrawler() { 15 | const opts = { 16 | name: this.name, 17 | term: this.term, 18 | sortType: this.sortType, 19 | directory: this.images_to, 20 | }; 21 | this.crawler = Mizu.createCrawler(opts); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/lib/crawlers/anime/DanbooruImageCrawler.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { Danbooru } = require('mizu'); 3 | 4 | const ImageCrawler = require(path.join('..', 'ImageCrawler')); 5 | const CRAWLING_SITE_NAME = 'danbooru'; 6 | 7 | module.exports = class DanbooruImageCrawler extends ImageCrawler { 8 | constructor(term) { 9 | const auth = { login: process.env.DANBOORU_USERNAME, api_key: process.env.DANBOORU_API_KEY }; 10 | super({ name: CRAWLING_SITE_NAME, term, auth }); 11 | this.setCrawler(Danbooru); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/crawlers/anime/SankakuComplexImageCrawler.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { SankakuComplex } = require('mizu'); 3 | 4 | const ImageCrawler = require(path.join('..', 'ImageCrawler')); 5 | const CRAWLING_SITE_NAME = 'sankaku_complex'; 6 | 7 | module.exports = class SankakuComplexImageCrawler extends ImageCrawler { 8 | constructor(term) { 9 | super({ name: CRAWLING_SITE_NAME, term }); 10 | this.setCrawler(SankakuComplex); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/lib/crawlers/anime/YandereImageCrawler.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { Yandere } = require('mizu'); 3 | 4 | const ImageCrawler = require(path.join('..', 'ImageCrawler')); 5 | const CRAWLING_SITE_NAME = 'yande_re'; 6 | 7 | module.exports = class YandereImageCrawler extends ImageCrawler { 8 | constructor(term) { 9 | super({ name: CRAWLING_SITE_NAME, term }); 10 | this.setCrawler(Yandere); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/lib/crawlers/index.js: -------------------------------------------------------------------------------- 1 | const AmatsukaImageCrawler = require('./anime/AmatsukaImageCrawler'); 2 | const DanbooruImageCrawler = require('./anime/DanbooruImageCrawler'); 3 | const DLSiteCrawler = require('./shop/DLSiteCrawler'); 4 | const SankakuComplexImageCrawler = require('./anime/SankakuComplexImageCrawler'); 5 | const YandereImageCrawler = require('./anime/YandereImageCrawler'); 6 | 7 | module.exports = { 8 | AmatsukaImageCrawler, 9 | DanbooruImageCrawler, 10 | DLSiteCrawler, 11 | SankakuComplexImageCrawler, 12 | YandereImageCrawler, 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/crawlers/shop/DLSiteCrawler.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { DLSite } = require('mizu'); 3 | 4 | const ImageCrawler = require(path.join('..', 'ImageCrawler')); 5 | const CRAWLING_SITE_NAME = 'dlsite'; 6 | 7 | module.exports = class DLSiteCrawler extends ImageCrawler { 8 | constructor(term, options) { 9 | super({ name: CRAWLING_SITE_NAME, term, options }); 10 | this.setCrawler(DLSite); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/lib/utils/Camera.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const puppeteer = require('puppeteer'); 3 | 4 | const { logger } = require(path.resolve('logger')); 5 | 6 | module.exports = class Camera { 7 | static async caputure(url, filepath) { 8 | const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] }); 9 | const page = await browser.newPage(); 10 | await page.setViewport({ 11 | width: 1200, 12 | height: 800, 13 | }); 14 | await page.goto(url, { waitUntil: 'networkidle0' }); 15 | 16 | // スクリーンショットを保存 17 | await page.screenshot({ 18 | path: filepath, 19 | type: 'jpeg', // JPEG形式で保存 20 | quality: 40, // 品質を0-100で指定 21 | }); 22 | await browser.close(); 23 | return filepath; 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/lib/utils/Delayer.js: -------------------------------------------------------------------------------- 1 | module.exports = class Delayer { 2 | static delayPromise(ms) { 3 | return new Promise((resolve => setTimeout(resolve, ms))); 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /src/lib/utils/ImageMeasure.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const probe = require('probe-image-size'); 4 | 5 | const { DIRECTORIES } = require(path.resolve('build', 'lib', 'constants')); 6 | 7 | module.exports = class ImageMeasure { 8 | static getImageSize(filename) { 9 | try { 10 | const filepath = `${DIRECTORIES.IMAGES_TO}/${filename}`; 11 | const input = fs.createReadStream(filepath); 12 | return probe(input); 13 | } catch (e) { 14 | return null; 15 | } 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /src/lib/utils/SlackLogger.js: -------------------------------------------------------------------------------- 1 | const Slack = require('slack-node'); 2 | const path = require('path'); 3 | 4 | require('dotenv').config(); 5 | 6 | module.exports = class SlackLogger { 7 | constructor({ 8 | channel = process.env.SLACK_LOGGER_CHANNEL, 9 | username = process.env.SLACK_LOGGER_USERNAME, 10 | webhookUri = process.env.SLACK_LOGGER_WEBHOOK_URL, 11 | } = {}) { 12 | this.channel = channel; 13 | this.username = username; 14 | this.webhookUri = webhookUri; 15 | this.slack = new Slack(); 16 | this.slack.setWebhook(webhookUri); 17 | return this; 18 | } 19 | 20 | send({ text, attachments = {} }) { 21 | return new Promise((resolve, reject) => { 22 | this.slack.webhook( 23 | { 24 | channel: this.channel, 25 | username: this.username, 26 | text, 27 | attachments, 28 | }, 29 | (err, response) => { 30 | if (err) { 31 | return reject(err); 32 | } 33 | return resolve(response); 34 | } 35 | ); 36 | }); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /src/lib/utils/TimeManager.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment'); 2 | 3 | module.exports = class TimeManager { 4 | /** 5 | * 6 | * @param {*} term 7 | * @param {*} date 8 | */ 9 | static normalizeDate({ term, date }) { 10 | if (!['days', 'weeks', 'months', 'years'].includes(term)) { 11 | throw new Error('TimeManager.normalizeDate() allow "days" or "weeks" or "months" or "years"'); 12 | } 13 | return moment(date).format('YYYY-MM-DD'); 14 | } 15 | 16 | /** 17 | * 18 | * @param {*} term 19 | * @param {*} date 20 | * @param {*} amount 21 | */ 22 | static changeDate({ term, date, amount }) { 23 | if (!['days', 'weeks', 'months', 'years'].includes(term)) { 24 | throw new Error('TimeManager.changeDate() allow "days" or "weeks" or "months" or "years"'); 25 | } 26 | return moment(date) 27 | .add(amount, term) 28 | .format('YYYY-MM-DD'); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/lib/utils/gasyo.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const imageType = require('image-type'); 3 | 4 | const gasyo = { 5 | is: { 6 | fetch: async (url) => { 7 | const response = await axios.get(url, { 8 | responseType: 'arraybuffer', 9 | }); 10 | 11 | if (response.status !== 200) { 12 | return false; 13 | } 14 | 15 | return imageType(response.data); 16 | }, 17 | jpg: async ({ url, type }) => { 18 | type = type || (await gasyo.is.fetch(url)); 19 | return type.ext === 'jpg'; 20 | }, 21 | png: async ({ url, type }) => { 22 | type = type || (await gasyo.is.fetch(url)); 23 | return type.ext === 'png'; 24 | }, 25 | bmp: async ({ url, type }) => { 26 | type = type || (await gasyo.is.fetch(url)); 27 | return type.ext === 'bmp'; 28 | }, 29 | gif: async ({ url, type }) => { 30 | type = type || (await gasyo.is.fetch(url)); 31 | return type.ext === 'gif'; 32 | }, 33 | webp: async ({ url, type }) => { 34 | type = type || (await gasyo.is.fetch(url)); 35 | return type.ext === 'webp'; 36 | }, 37 | image: async (url) => { 38 | const type = await gasyo.is.fetch(url); 39 | return ( 40 | (await gasyo.is.jpg({ type })) || 41 | (await gasyo.is.png({ type })) || 42 | (await gasyo.is.bmp({ type })) 43 | ); 44 | }, 45 | }, 46 | }; 47 | 48 | module.exports = gasyo; 49 | -------------------------------------------------------------------------------- /src/model/cache/redisClient.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const bluebird = require('bluebird'); 3 | const redis = require('redis'); 4 | 5 | require('dotenv').config(); 6 | 7 | let redisClient = null; 8 | if (process.env.REDIS_URI) { 9 | redisClient = redis.createClient(process.env.REDIS_URI); 10 | } else { 11 | redisClient = redis.createClient(); 12 | } 13 | if (process.env.REDIS_PASSWORD) { 14 | redisClient.auth(process.env.REDIS_PASSWORD, (err) => { 15 | if (err) throw err; 16 | }); 17 | } 18 | 19 | redisClient.on('connect', () => { 20 | console.log('Connected to Redis'); 21 | }); 22 | 23 | bluebird.promisifyAll(redis.RedisClient.prototype); 24 | bluebird.promisifyAll(redis.Multi.prototype); 25 | 26 | module.exports = redisClient; 27 | -------------------------------------------------------------------------------- /src/model/lib/DatabaseProviderFactory.js: -------------------------------------------------------------------------------- 1 | const { 2 | ArchiveProvider, 3 | UserProvider, 4 | PostProvider, 5 | DonePostProvider, 6 | FavoriteProvider, 7 | DoneHistoryProvider, 8 | ImageProvider, 9 | ImageRelativeProvider, 10 | PopularProvider, 11 | VideoProvider, 12 | } = require('../provider'); 13 | 14 | module.exports = class DatabaseProviderFactory { 15 | static createProvider(name) { 16 | switch (name.toLowerCase()) { 17 | case 'archive': 18 | return new ArchiveProvider(); 19 | case 'user': 20 | return new UserProvider(); 21 | case 'post': 22 | return new PostProvider(); 23 | case 'done': 24 | return new DonePostProvider(); 25 | case 'favorite': 26 | return new FavoriteProvider(); 27 | case 'donehistory': 28 | return new DoneHistoryProvider(); 29 | case 'image': 30 | return new ImageProvider(); 31 | case 'imageRelative': 32 | return new ImageRelativeProvider(); 33 | case 'popular': 34 | return new PopularProvider(); 35 | case 'video': 36 | return new VideoProvider(); 37 | default: 38 | return new PostProvider(); // CAUTION: nullの方がいい? 39 | } 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /src/model/lib/MongoDBTimeNormalizer.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment'); 2 | 3 | module.exports = class MongoDBTimeNormalizer { 4 | // @JSTDate: (date) -> return moment(date).subtract(9, 'hours').format('YYYY-MM-DD') 5 | static JSTDate(date) { 6 | return date; 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/model/lib/MongooseObjectNormalizer.js: -------------------------------------------------------------------------------- 1 | module.exports = class MongooseObjectNormalizer { 2 | static shape(instance) { 3 | instance = instance.toObject(); 4 | delete instance._id; 5 | delete instance.__v; 6 | return instance; 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/model/provider/image.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Schema = mongoose.Schema; 4 | const ObjectId = Schema.ObjectId; 5 | const DBBaseProvider = require('./base'); 6 | const { Image } = require('../schemas'); 7 | 8 | module.exports = class ImageProvider extends DBBaseProvider { 9 | constructor() { 10 | super(Image); 11 | } 12 | 13 | findByIdAndUpdate({ _id, image }) { 14 | const data = image; 15 | const options = { new: true, upsert: true }; 16 | return super.findByIdAndUpdate(_id, data, options); 17 | } 18 | 19 | findOneAndUpdate({ image }) { 20 | const query = { original: image.original }; 21 | const data = image; 22 | const options = { new: true, upsert: true }; 23 | return super.findOneAndUpdate(query, data, options); 24 | } 25 | 26 | upsert({ _id, image }) { 27 | const query = { _id }; 28 | const data = image; 29 | const options = { upsert: true }; 30 | return this.update(query, data, options); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/model/provider/imageRelative.js: -------------------------------------------------------------------------------- 1 | // const compare = require('hamming-distance'); 2 | // const { Post } = require('../schemas'); 3 | 4 | // const Schema = mongoose.Schema; 5 | // const ObjectId = Schema.ObjectId; 6 | // const DBBaseProvider = require('./base'); 7 | 8 | // const THRESHOLD = 10; 9 | 10 | // module.exports = class ImageRelativeProvider extends DBBaseProvider { 11 | // find({ userObjectId, archived, hashHexDecimal }) { 12 | // return new Promise((resolve, reject) => { 13 | // const condition = [ 14 | // { 15 | // postedBy: userObjectId, 16 | // isArchive: archived || false, 17 | // }, 18 | // ]; 19 | 20 | // return Post.find({ $and: condition }) 21 | // .populate('images') 22 | // .populate('videos') 23 | // .populate('postedBy', '-accessToken') 24 | // .$where(compare(hashHexDecimal, this.images.hashHexDecimal) < THRESHOLD) 25 | // .exec((err, post) => { 26 | // if (err) { 27 | // return reject(err); 28 | // } 29 | // return resolve(post); 30 | // }); 31 | // }); 32 | // } 33 | // }; 34 | -------------------------------------------------------------------------------- /src/model/provider/index.js: -------------------------------------------------------------------------------- 1 | const BaseProvider = require('./base'); 2 | const ArchiveProvider = require('./archive'); 3 | const UserProvider = require('./user'); 4 | const PostProvider = require('./post'); 5 | const DonePostProvider = require('./donePost'); 6 | const FavoriteProvider = require('./favorite'); 7 | const DoneHistoryProvider = require('./doneHistory'); 8 | const ImageProvider = require('./image'); 9 | const PopularProvider = require('./popular'); 10 | const RankingProvider = require('./ranking'); 11 | const TagProvider = require('./tag'); 12 | const VideoProvider = require('./video'); 13 | 14 | module.exports = { 15 | BaseProvider, 16 | ArchiveProvider, 17 | UserProvider, 18 | PostProvider, 19 | DonePostProvider, 20 | FavoriteProvider, 21 | DoneHistoryProvider, 22 | ImageProvider, 23 | PopularProvider, 24 | RankingProvider, 25 | TagProvider, 26 | VideoProvider, 27 | }; 28 | -------------------------------------------------------------------------------- /src/model/provider/tag.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Schema = mongoose.Schema; 4 | const DBBaseProvider = require('./base'); 5 | const { Tag } = require('../schemas'); 6 | 7 | module.exports = class TagProvider extends DBBaseProvider { 8 | constructor() { 9 | super(Tag); 10 | } 11 | 12 | findByIdAndUpdate({ _id, data }) { 13 | const options = { new: true, upsert: true }; 14 | return super.findByIdAndUpdate(_id, data, options); 15 | } 16 | 17 | upsert({ _id, data }) { 18 | const query = { _id }; 19 | const options = { upsert: true }; 20 | return this.update(query, data, options); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/model/provider/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const path = require('path'); 3 | 4 | const Schema = mongoose.Schema; 5 | const DBBaseProvider = require('./base'); 6 | const { User } = require('../schemas'); 7 | 8 | module.exports = class UserProvider extends DBBaseProvider { 9 | constructor() { 10 | super(User); 11 | } 12 | 13 | findByTwitterIdStr({ twitterIdStr }) { 14 | return this.findOne({ twitterIdStr }); 15 | } 16 | 17 | findByName({ name }) { 18 | return this.findOne({ name }, { accessToken: 0 }); 19 | } 20 | 21 | findByAccessToken({ accessToken }) { 22 | return this.findOne({ accessToken }, { accessToken: 0 }); 23 | } 24 | 25 | findAll() { 26 | return this.find({}, { accessToken: 0 }); 27 | } 28 | 29 | upsert({ user }) { 30 | const query = { twitterIdStr: user.twitterIdStr }; 31 | const data = Object.assign( 32 | { 33 | updatedAt: Date.now(), 34 | createdAt: Date.now(), 35 | }, 36 | user 37 | ); 38 | const options = { upsert: true }; 39 | this.update(query, data, options); 40 | } 41 | 42 | findOneAndUpdate({ user }) { 43 | const query = { twitterIdStr: user.twitterIdStr }; 44 | const data = user; 45 | const options = { new: true, upsert: true }; 46 | return super.findOneAndUpdate(query, data, options); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /src/model/provider/video.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Schema = mongoose.Schema; 4 | const ObjectId = Schema.ObjectId; 5 | const DBBaseProvider = require('./base'); 6 | const { Video } = require('../schemas'); 7 | 8 | module.exports = class VideoProvider extends DBBaseProvider { 9 | constructor() { 10 | super(Video); 11 | } 12 | 13 | findByIdAndUpdate({ _id, video }) { 14 | const data = video; 15 | const options = { new: true, upsert: true }; 16 | return super.findByIdAndUpdate(_id, data, options); 17 | } 18 | 19 | findOneAndUpdate({ video }) { 20 | const query = { original: video.original }; 21 | const data = video; 22 | const options = { new: true, upsert: true }; 23 | return super.findOneAndUpdate(query, data, options); 24 | } 25 | 26 | upsert({ _id, video }) { 27 | const query = { _id }; 28 | const data = video; 29 | const options = { upsert: true }; 30 | return this.update(query, data, options); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/model/repository/DoneHistoryRepository.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { DoneHistoryProvider } = require(path.resolve('build', 'model', 'provider')); 4 | 5 | module.exports = class DoneHistoryRepository extends DoneHistoryProvider { 6 | constructor(doneHistory) { 7 | if (doneHistory == null) { 8 | doneHistory = {}; 9 | } 10 | super(...arguments); 11 | this.doneHistory = doneHistory; 12 | } 13 | 14 | setDoneHistory(doneHistory) { 15 | return (this.doneHistory = doneHistory); 16 | } 17 | 18 | getDoneHistory(doneHistory) { 19 | return this.doneHistory; 20 | } 21 | 22 | setDonePostObjectId(donePostObjectId) { 23 | return (this.donePostObjectId = donePostObjectId); 24 | } 25 | 26 | getDonePostObjectId() { 27 | return this.donePostObjectId; 28 | } 29 | 30 | __save() { 31 | return this.save({ doneHistory: this.doneHistory }); 32 | } 33 | 34 | __removeById() { 35 | return this.removeById({ doneHistoryObjectId: this.doneHistory._id }); 36 | } 37 | 38 | __removeByDonePostObjectId() { 39 | return this.removeByDonePostObjectId({ donePostObjectId: this.donePostObjectId }); 40 | } 41 | 42 | __findLatestByDonePostObjectID() { 43 | return this.findLatestByDonePostObjectID({ donePostObjectId: this.donePostObjectId }); 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /src/model/repository/ImageRepository.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { ImageProvider } = require(path.resolve('build', 'model', 'provider')); 4 | 5 | module.exports = class ImageRepository extends ImageProvider { 6 | constructor(image = []) { 7 | super(...arguments); 8 | this.image = image; 9 | } 10 | 11 | setImage(image) { 12 | this.image = image; 13 | } 14 | 15 | __findOneAndUpdate() { 16 | return this.findOneAndUpdate({ image: this.image }); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /src/model/repository/UserRepository.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { UserProvider } = require(path.resolve('build', 'model', 'provider')); 4 | 5 | module.exports = class UserRepository extends UserProvider { 6 | constructor(twitterIdStr) { 7 | if (twitterIdStr == null) { 8 | twitterIdStr = null; 9 | } 10 | super(...arguments); 11 | this.twitterIdStr = twitterIdStr; 12 | } 13 | 14 | setUser(user) { 15 | return (this.user = user); 16 | } 17 | 18 | getUser(user) { 19 | return this.user; 20 | } 21 | 22 | setTwitterIdStr(twitterIdStr) { 23 | return (this.twitterIdStr = twitterIdStr); 24 | } 25 | 26 | getTwitterIdStr(twitterIdStr) { 27 | return this.twitterIdStr; 28 | } 29 | 30 | __findByTwitterIdStr() { 31 | return this.findByTwitterIdStr({ twitterIdStr: this.twitterIdStr }); 32 | } 33 | 34 | __findByAccessToken(accessToken) { 35 | return this.findByAccessToken({ accessToken }); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /src/model/repository/VideoRepository.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { VideoProvider } = require(path.resolve('build', 'model', 'provider')); 4 | 5 | module.exports = class VideoRepository extends VideoProvider { 6 | constructor(video = {}) { 7 | super(...arguments); 8 | this.video = video; 9 | } 10 | 11 | setVideo(video) { 12 | this.video = video; 13 | } 14 | 15 | __findOneAndUpdate() { 16 | return this.findOneAndUpdate({ video: this.video }); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /src/model/repository/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | DoneHistoryRepository: require('./DoneHistoryRepository'), 3 | DashboardRepository: require('./DashboardRepository'), 4 | DonePostRepository: require('./DonePostRepository'), 5 | TrasnportRepository: require('./TrasnportRepository'), 6 | ImageRepository: require('./ImageRepository'), 7 | PostRepository: require('./PostRepository'), 8 | StatisticsRepository: require('./StatisticsRepository'), 9 | UserRepository: require('./UserRepository'), 10 | VideoRepository: require('./VideoRepository'), 11 | }; 12 | -------------------------------------------------------------------------------- /src/model/schemas/DoneHistory.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Schema = mongoose.Schema; 4 | const ObjectId = Schema.ObjectId; 5 | 6 | const DoneHistorySchema = new Schema({ 7 | donePost: { 8 | type: ObjectId, 9 | ref: 'DonePost', 10 | index: true, 11 | }, 12 | postedBy: { 13 | type: ObjectId, 14 | ref: 'User', 15 | index: true, 16 | }, 17 | originPostedBy: { 18 | type: ObjectId, 19 | ref: 'User', 20 | index: true, 21 | }, 22 | createdAt: { 23 | type: Date, 24 | default: Date.now(), 25 | }, 26 | updatedAt: { 27 | type: Date, 28 | default: Date.now(), 29 | }, 30 | }); 31 | 32 | mongoose.model('DoneHistory', DoneHistorySchema); 33 | 34 | const DoneHistory = mongoose.model('DoneHistory'); 35 | 36 | module.exports = DoneHistory; 37 | -------------------------------------------------------------------------------- /src/model/schemas/Image.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Schema = mongoose.Schema; 4 | const ObjectId = Schema.ObjectId; 5 | 6 | const ImageSchema = new Schema({ 7 | original: String, // original 8 | tiny: String, // 30 9 | mini: String, // 120 10 | small: String, // 240 11 | medium: String, // 480 12 | large: String, // 1280 13 | hashHexDecimal: String, 14 | }); // 16進数のハッシュ値(jimp.hash()で生成した値) 15 | 16 | ImageSchema.index({ original: -1 }); 17 | 18 | mongoose.model('Image', ImageSchema); 19 | 20 | const Image = mongoose.model('Image'); 21 | 22 | module.exports = Image; 23 | -------------------------------------------------------------------------------- /src/model/schemas/Ranking.js: -------------------------------------------------------------------------------- 1 | // TODO: VIEWに置き換え 2 | 3 | const mongoose = require('mongoose'); 4 | 5 | const Schema = mongoose.Schema; 6 | const ObjectId = Schema.ObjectId; 7 | 8 | const RankingSchema = new Schema({ 9 | date: { 10 | type: String, // Yearly->2018/2017/2016, Monthly->2018/12, 2018/11 11 | }, 12 | item: { 13 | type: String, 14 | }, 15 | count: { 16 | type: Number, 17 | }, 18 | postedBy: { 19 | type: ObjectId, 20 | ref: 'User', 21 | index: true, 22 | }, 23 | createdAt: { 24 | type: Date, 25 | default: Date.now(), 26 | }, 27 | updatedAt: { 28 | type: Date, 29 | default: Date.now(), 30 | }, 31 | }); 32 | 33 | mongoose.model('Ranking', RankingSchema); 34 | 35 | const Ranking = mongoose.model('Ranking'); 36 | 37 | module.exports = Ranking; 38 | -------------------------------------------------------------------------------- /src/model/schemas/Tag.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Schema = mongoose.Schema; 4 | const ObjectId = Schema.ObjectId; 5 | 6 | // # 7 | // Schemaインタフェースを通してモデルの定義を行う 8 | // # 9 | const TagSchema = new Schema({ 10 | mean: { 11 | type: String, 12 | unique: true, 13 | index: true, 14 | }, 15 | ruby: String, 16 | }); 17 | 18 | // # 19 | // モデルへのアクセス 20 | // mongoose.model 'モデル名', 定義したスキーマクラス 21 | // を通して一度モデルを定義すると、同じ関数を通してアクセスすることができる 22 | // # 23 | mongoose.model('Tag', TagSchema); 24 | 25 | // # 26 | // 定義した時の登録名で呼び出し 27 | // # 28 | const Tag = mongoose.model('Tag'); 29 | 30 | module.exports = Tag; 31 | -------------------------------------------------------------------------------- /src/model/schemas/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Schema = mongoose.Schema; 4 | const ObjectId = Schema.ObjectId; 5 | 6 | // # 7 | // Schemaインタフェースを通してモデルの定義を行う 8 | // # 9 | const UserSchema = new Schema({ 10 | twitterIdStr: { 11 | type: String, 12 | unique: true, 13 | index: true, 14 | }, 15 | name: String, 16 | screenName: String, 17 | icon: String, 18 | url: String, 19 | description: String, 20 | accessToken: String, 21 | accessTokenSecret: String, 22 | isPremium: { 23 | type: Boolean, 24 | default: false, 25 | }, 26 | createdAt: { 27 | type: Date, 28 | default: Date.now(), 29 | }, 30 | updatedAt: { 31 | type: Date, 32 | default: Date.now(), 33 | }, 34 | }); 35 | 36 | // # 37 | // モデルへのアクセス 38 | // mongoose.model 'モデル名', 定義したスキーマクラス 39 | // を通して一度モデルを定義すると、同じ関数を通してアクセスすることができる 40 | // # 41 | mongoose.model('User', UserSchema); 42 | 43 | // # 44 | // 定義した時の登録名で呼び出し 45 | // # 46 | const User = mongoose.model('User'); 47 | 48 | module.exports = User; 49 | -------------------------------------------------------------------------------- /src/model/schemas/Video.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Schema = mongoose.Schema; 4 | const ObjectId = Schema.ObjectId; 5 | 6 | const VideoSchema = new Schema({ 7 | original: String, // original 8 | }); 9 | 10 | VideoSchema.index({ original: -1 }, { unique: true }); 11 | 12 | mongoose.model('Video', VideoSchema); 13 | 14 | const Video = mongoose.model('Video'); 15 | 16 | module.exports = Video; 17 | -------------------------------------------------------------------------------- /src/model/schemas/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | DoneHistory: require('./DoneHistory'), 3 | DonePost: require('./DonePost'), 4 | Image: require('./Image'), 5 | Post: require('./Post'), 6 | Ranking: require('./Ranking'), 7 | User: require('./User'), 8 | Tag: require('./Tag'), 9 | Video: require('./Video'), 10 | }; 11 | -------------------------------------------------------------------------------- /src/script/crawl_batch/CrawlBatchForImageBoardExecuter.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { DanbooruImageCrawler, YandereImageCrawler } = require(path.resolve('build', 'lib', 'crawlers')); 4 | 5 | module.exports = class CrawlBatchForImageBoardExecuter { 6 | constructor(term) { 7 | this.term = term; 8 | } 9 | 10 | get crawlTasks() { 11 | if (this.term === 'days') { 12 | return [new YandereImageCrawler(this.term), new DanbooruImageCrawler(this.term)]; 13 | } 14 | return [new YandereImageCrawler(this.term), new DanbooruImageCrawler(this.term)]; 15 | } 16 | 17 | exec() { 18 | Promise.allSettled(this.crawlTasks.map((task) => task.exec())) 19 | .then((result) => { 20 | console.log('Fin CrawlBatchForImageBoardExecuter'); 21 | process.exit(0); 22 | }) 23 | .catch((err) => { 24 | console.error(err); 25 | process.exit(1); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/script/crawl_batch/daily/DailyBatchExecuter.js: -------------------------------------------------------------------------------- 1 | const CrawlBatchForImageBoardExecuter = require('../CrawlBatchForImageBoardExecuter'); 2 | 3 | const TERM = 'days'; 4 | 5 | new CrawlBatchForImageBoardExecuter(TERM).exec(); 6 | -------------------------------------------------------------------------------- /src/script/crawl_batch/monthly/MonthlyBatchExecuter.js: -------------------------------------------------------------------------------- 1 | const CrawlBatchForImageBoardExecuter = require('../CrawlBatchForImageBoardExecuter'); 2 | 3 | const TERM = 'months'; 4 | 5 | new CrawlBatchForImageBoardExecuter(TERM).exec(); 6 | -------------------------------------------------------------------------------- /src/script/crawl_batch/registerUnearth.js: -------------------------------------------------------------------------------- 1 | // const path = require('path'); 2 | 3 | // const redisClient = require(path.resolve( 4 | // 'build', 5 | // 'model', 6 | // 'cache', 7 | // 'redisClient', 8 | // )); 9 | // const { logger } = require(path.resolve('logger')); 10 | // const { UserProvider } = require(path.resolve('build', 'model', 'provider')); 11 | 12 | // (async () => { 13 | // const crawler = new TweetCrawler(); 14 | 15 | // try { 16 | // const users = await new UserProvider().findAll(); 17 | // for (const user of users) { 18 | // const types = ['inbox', 'archive', 'done'] 19 | // const terms = [...Array(10).keys()].map(i => ++i).map(i => i * 12) 20 | // const condition = UnearthService.Condition({}); 21 | // await UnearthService.find('day', condition); 22 | // } 23 | // } catch (e) {} 24 | // })(); 25 | -------------------------------------------------------------------------------- /src/script/crawl_batch/weekly/WeeklyBatchExecuter.js: -------------------------------------------------------------------------------- 1 | const CrawlBatchForImageBoardExecuter = require('../CrawlBatchForImageBoardExecuter'); 2 | 3 | const TERM = 'weeks'; 4 | 5 | new CrawlBatchForImageBoardExecuter(TERM).exec(); 6 | -------------------------------------------------------------------------------- /start.bat: -------------------------------------------------------------------------------- 1 | docker-compose up -d --build --------------------------------------------------------------------------------