├── 01-env └── mangarel-demo │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .firebaserc.sample │ ├── .gitignore │ ├── .prettierrc │ ├── .vscode │ ├── extensions.json │ └── settings.json │ ├── README.md │ ├── firebase.json │ ├── firestore.indexes.json │ ├── firestore.rules │ ├── functions │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .node-version │ ├── .vscode │ │ ├── extensions.json │ │ ├── settings.json │ │ └── tasks.json │ ├── package.json │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ └── yarn.lock │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt │ ├── src │ ├── App.css │ ├── App.tsx │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── react-app-env.d.ts │ └── serviceWorker.ts │ ├── stylelint.config.js │ ├── tsconfig.json │ └── yarn.lock ├── 02-seed └── mangarel-demo │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .firebaserc.sample │ ├── .gitignore │ ├── .prettierrc │ ├── .vscode │ ├── extensions.json │ └── settings.json │ ├── README.md │ ├── firebase.json │ ├── firestore.indexes.json │ ├── firestore.rules │ ├── functions │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .node-version │ ├── .vscode │ │ ├── extensions.json │ │ ├── settings.json │ │ └── tasks.json │ ├── package.json │ ├── seeds │ │ └── publishers.tsv │ ├── src │ │ ├── commands │ │ │ └── dbseed.ts │ │ ├── firestore-admin │ │ │ └── record-counter.ts │ │ ├── index.ts │ │ ├── services │ │ │ └── mangarel │ │ │ │ ├── constants.ts │ │ │ │ └── models │ │ │ │ └── publisher.ts │ │ └── utils │ ├── tsconfig.json │ └── yarn.lock │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt │ ├── src │ ├── App.css │ ├── App.tsx │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── react-app-env.d.ts │ └── serviceWorker.ts │ ├── stylelint.config.js │ ├── tsconfig.json │ └── yarn.lock ├── 03-functions ├── 01-publishers │ └── mangarel-demo │ │ ├── .eslintignore │ │ ├── .eslintrc.js │ │ ├── .firebaserc.sample │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── .vscode │ │ ├── extensions.json │ │ └── settings.json │ │ ├── README.md │ │ ├── firebase.json │ │ ├── firestore.indexes.json │ │ ├── firestore.rules │ │ ├── functions │ │ ├── .eslintignore │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .node-version │ │ ├── .vscode │ │ │ ├── extensions.json │ │ │ ├── settings.json │ │ │ └── tasks.json │ │ ├── package.json │ │ ├── seeds │ │ │ └── publishers.tsv │ │ ├── src │ │ │ ├── commands │ │ │ │ └── dbseed.ts │ │ │ ├── firestore-admin │ │ │ │ └── record-counter.ts │ │ │ ├── index.ts │ │ │ └── services │ │ │ │ └── mangarel │ │ │ │ ├── constants.ts │ │ │ │ └── models │ │ │ │ └── publisher.ts │ │ ├── tsconfig.json │ │ └── yarn.lock │ │ ├── package.json │ │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ ├── logo.svg │ │ ├── react-app-env.d.ts │ │ └── serviceWorker.ts │ │ ├── stylelint.config.js │ │ ├── tsconfig.json │ │ └── yarn.lock ├── 02-crawler │ └── mangarel-demo │ │ ├── .eslintignore │ │ ├── .eslintrc.js │ │ ├── .firebaserc.sample │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── .vscode │ │ ├── extensions.json │ │ └── settings.json │ │ ├── README.md │ │ ├── firebase.json │ │ ├── firestore.indexes.json │ │ ├── firestore.rules │ │ ├── functions │ │ ├── .eslintignore │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .node-version │ │ ├── .vscode │ │ │ ├── extensions.json │ │ │ ├── settings.json │ │ │ └── tasks.json │ │ ├── package.json │ │ ├── seeds │ │ │ └── publishers.tsv │ │ ├── src │ │ │ ├── commands │ │ │ │ └── dbseed.ts │ │ │ ├── crawlers │ │ │ │ └── kodansha-calendar.ts │ │ │ ├── firestore-admin │ │ │ │ ├── author.ts │ │ │ │ ├── book.ts │ │ │ │ ├── feed-memo.ts │ │ │ │ ├── publisher.ts │ │ │ │ └── record-counter.ts │ │ │ ├── index.ts │ │ │ ├── services │ │ │ │ ├── mangarel │ │ │ │ │ ├── constants.ts │ │ │ │ │ └── models │ │ │ │ │ │ ├── author.ts │ │ │ │ │ │ ├── book.ts │ │ │ │ │ │ ├── feed-memo.ts │ │ │ │ │ │ └── publisher.ts │ │ │ │ └── rakuten │ │ │ │ │ ├── api.ts │ │ │ │ │ └── models │ │ │ │ │ └── book-item.ts │ │ │ └── utils │ │ │ │ ├── env.ts │ │ │ │ ├── text-processor.ts │ │ │ │ └── timer.ts │ │ ├── tsconfig.json │ │ └── yarn.lock │ │ ├── package.json │ │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ ├── logo.svg │ │ ├── react-app-env.d.ts │ │ └── serviceWorker.ts │ │ ├── stylelint.config.js │ │ ├── tsconfig.json │ │ └── yarn.lock └── 04-advanced │ └── mangarel-demo │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .firebaserc.sample │ ├── .gitignore │ ├── .prettierrc │ ├── .vscode │ ├── extensions.json │ └── settings.json │ ├── README.md │ ├── firebase.json │ ├── firestore.indexes.json │ ├── firestore.rules │ ├── functions │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .node-version │ ├── .runtimeconfig.sample.json │ ├── .vscode │ │ ├── extensions.json │ │ ├── settings.json │ │ └── tasks.json │ ├── package.json │ ├── seeds │ │ └── publishers.tsv │ ├── src │ │ ├── commands │ │ │ └── dbseed.ts │ │ ├── crawlers │ │ │ └── kodansha-calendar.ts │ │ ├── fetch-calendar.ts │ │ ├── firestore-admin │ │ │ ├── author.ts │ │ │ ├── book.ts │ │ │ ├── feed-memo.ts │ │ │ ├── publisher.ts │ │ │ └── record-counter.ts │ │ ├── index.ts │ │ ├── publishers.ts │ │ ├── register-books.ts │ │ ├── services │ │ │ ├── mangarel │ │ │ └── rakuten │ │ │ │ ├── api.ts │ │ │ │ └── models │ │ │ │ └── book-item.ts │ │ └── utils │ ├── tsconfig.json │ └── yarn.lock │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt │ ├── src │ ├── App.css │ ├── App.tsx │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── react-app-env.d.ts │ ├── serviceWorker.ts │ ├── services │ │ └── mangarel │ │ │ ├── constants.ts │ │ │ └── models │ │ │ ├── author.ts │ │ │ ├── book.ts │ │ │ ├── feed-memo.ts │ │ │ └── publisher.ts │ └── utils │ │ ├── env.ts │ │ ├── text-processor.ts │ │ └── timer.ts │ ├── stylelint.config.js │ ├── tsconfig.json │ └── yarn.lock ├── 04-firestore └── mangarel-demo │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .firebaserc.sample │ ├── .gitignore │ ├── .prettierrc │ ├── .vscode │ ├── extensions.json │ └── settings.json │ ├── README.md │ ├── firebase.json │ ├── firestore.indexes.json │ ├── firestore.rules │ ├── functions │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .node-version │ ├── .runtimeconfig.sample.json │ ├── .vscode │ │ ├── extensions.json │ │ ├── settings.json │ │ └── tasks.json │ ├── package.json │ ├── seeds │ │ └── publishers.tsv │ ├── src │ │ ├── commands │ │ │ └── dbseed.ts │ │ ├── crawlers │ │ │ └── kodansha-calendar.ts │ │ ├── fetch-calendar.ts │ │ ├── firestore-admin │ │ │ ├── author.ts │ │ │ ├── book.ts │ │ │ ├── feed-memo.ts │ │ │ ├── publisher.ts │ │ │ └── record-counter.ts │ │ ├── index.ts │ │ ├── publishers.ts │ │ ├── register-books.ts │ │ ├── search-books.ts │ │ ├── services │ │ │ ├── mangarel │ │ │ └── rakuten │ │ │ │ ├── api.ts │ │ │ │ └── models │ │ │ │ └── book-item.ts │ │ └── utils │ ├── tsconfig.json │ └── yarn.lock │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt │ ├── src │ ├── App.css │ ├── App.tsx │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── react-app-env.d.ts │ ├── serviceWorker.ts │ ├── services │ │ └── mangarel │ │ │ ├── constants.ts │ │ │ └── models │ │ │ ├── author.ts │ │ │ ├── book.ts │ │ │ ├── feed-memo.ts │ │ │ └── publisher.ts │ └── utils │ │ ├── env.ts │ │ ├── n-gram.spec.ts │ │ ├── n-gram.ts │ │ ├── text-processor.spec.ts │ │ ├── text-processor.ts │ │ └── timer.ts │ ├── stylelint.config.js │ ├── tsconfig.json │ └── yarn.lock ├── 05-react └── mangarel-demo │ ├── .env.sample │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .firebaserc.sample │ ├── .gitignore │ ├── .prettierrc │ ├── .vscode │ ├── extensions.json │ └── settings.json │ ├── README.md │ ├── firebase.json │ ├── firestore.indexes.json │ ├── firestore.rules │ ├── functions │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .node-version │ ├── .runtimeconfig.sample.json │ ├── .vscode │ │ ├── extensions.json │ │ ├── settings.json │ │ └── tasks.json │ ├── package.json │ ├── seeds │ │ └── publishers.tsv │ ├── src │ │ ├── commands │ │ │ └── dbseed.ts │ │ ├── crawlers │ │ │ └── kodansha-calendar.ts │ │ ├── fetch-calendar.ts │ │ ├── firestore-admin │ │ │ ├── author.ts │ │ │ ├── book.ts │ │ │ ├── feed-memo.ts │ │ │ ├── publisher.ts │ │ │ └── record-counter.ts │ │ ├── index.ts │ │ ├── publishers.ts │ │ ├── register-books.ts │ │ ├── search-books.ts │ │ ├── services │ │ │ ├── mangarel │ │ │ └── rakuten │ │ │ │ ├── api.ts │ │ │ │ └── models │ │ │ │ └── book-item.ts │ │ └── utils │ ├── tsconfig.json │ └── yarn.lock │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── images │ │ ├── comingsoon-large.png │ │ ├── comingsoon-small.png │ │ └── rakuten-logo.svg │ ├── index.html │ ├── mangarel-logo192.png │ ├── mangarel-logo512.png │ ├── manifest.json │ └── robots.txt │ ├── src │ ├── App.css │ ├── App.tsx │ ├── FirebaseApp.tsx │ ├── components │ │ ├── Book │ │ │ ├── BookMain.tsx │ │ │ ├── RakutenBooksButton.tsx │ │ │ ├── RegisterCalenderButton.tsx │ │ │ └── index.tsx │ │ ├── Home │ │ │ ├── Calendar.tsx │ │ │ └── index.tsx │ │ ├── Search │ │ │ ├── SearchForm.tsx │ │ │ └── index.tsx │ │ └── common │ │ │ ├── atoms │ │ │ ├── CardAttribute.tsx │ │ │ ├── CardContent.tsx │ │ │ ├── CardDescription.tsx │ │ │ ├── CardInfo.tsx │ │ │ ├── CardSummary.tsx │ │ │ ├── CardTitle.tsx │ │ │ ├── ItemCard.tsx │ │ │ ├── LargeCoverImage.tsx │ │ │ ├── LinkButton.tsx │ │ │ ├── ListLoader.tsx │ │ │ ├── MainFrame.tsx │ │ │ ├── SmallCoverImage.tsx │ │ │ ├── Spacer.tsx │ │ │ ├── ToggleButton.tsx │ │ │ └── WideButton.tsx │ │ │ ├── card │ │ │ └── BookCard.tsx │ │ │ ├── header │ │ │ └── DividingHeader.tsx │ │ │ ├── item-tools.ts │ │ │ ├── list │ │ │ ├── BookList.tsx │ │ │ └── CalendarList.tsx │ │ │ ├── menubar │ │ │ └── NavigationBar.tsx │ │ │ └── work │ │ │ ├── AttributesField.tsx │ │ │ ├── ButtonGroup.tsx │ │ │ ├── CardGroup.tsx │ │ │ ├── WorkAuthors.tsx │ │ │ ├── WorkPlaceHolder.tsx │ │ │ ├── WorkPublishedOn.tsx │ │ │ └── WorkTitle.tsx │ ├── containers │ │ ├── Book │ │ │ └── BookMain.tsx │ │ ├── Home │ │ │ └── Calendar.tsx │ │ └── Search │ │ │ └── SearchUnit.tsx │ ├── contexts.ts │ ├── firebase-config.ts │ ├── hooks │ │ ├── use-book-search.ts │ │ ├── use-book.ts │ │ └── use-books.ts │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── paths.ts │ ├── react-app-env.d.ts │ ├── serviceWorker.ts │ ├── services │ │ └── mangarel │ │ │ ├── constants.ts │ │ │ └── models │ │ │ ├── author.ts │ │ │ ├── book.ts │ │ │ ├── feed-memo.ts │ │ │ └── publisher.ts │ ├── theme.ts │ └── utils │ │ ├── env.ts │ │ ├── n-gram.spec.ts │ │ ├── n-gram.ts │ │ ├── text-processor.spec.ts │ │ ├── text-processor.ts │ │ └── timer.ts │ ├── stylelint.config.js │ ├── tsconfig.json │ └── yarn.lock ├── 06-auth └── mangarel-demo │ ├── .env.sample │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .firebaserc.sample │ ├── .gitignore │ ├── .prettierrc │ ├── .vscode │ ├── extensions.json │ └── settings.json │ ├── README.md │ ├── firebase.json │ ├── firestore.indexes.json │ ├── firestore.rules │ ├── functions │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .node-version │ ├── .runtimeconfig.sample.json │ ├── .vscode │ │ ├── extensions.json │ │ ├── settings.json │ │ └── tasks.json │ ├── package.json │ ├── seeds │ │ └── publishers.tsv │ ├── src │ │ ├── commands │ │ │ └── dbseed.ts │ │ ├── crawlers │ │ │ └── kodansha-calendar.ts │ │ ├── fetch-calendar.ts │ │ ├── firestore-admin │ │ │ ├── author.ts │ │ │ ├── book.ts │ │ │ ├── feed-memo.ts │ │ │ ├── publisher.ts │ │ │ └── record-counter.ts │ │ ├── index.ts │ │ ├── publishers.ts │ │ ├── register-books.ts │ │ ├── search-books.ts │ │ ├── services │ │ │ ├── mangarel │ │ │ └── rakuten │ │ │ │ ├── api.ts │ │ │ │ └── models │ │ │ │ └── book-item.ts │ │ └── utils │ ├── tsconfig.json │ └── yarn.lock │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── images │ │ ├── comingsoon-large.png │ │ ├── comingsoon-small.png │ │ └── rakuten-logo.svg │ ├── index.html │ ├── mangarel-logo192.png │ ├── mangarel-logo512.png │ ├── manifest.json │ └── robots.txt │ ├── src │ ├── App.css │ ├── App.tsx │ ├── FirebaseApp.tsx │ ├── components │ │ ├── Book │ │ │ ├── BookMain.tsx │ │ │ ├── RakutenBooksButton.tsx │ │ │ ├── RegisterCalenderButton.tsx │ │ │ └── index.tsx │ │ ├── Home │ │ │ ├── Calendar.tsx │ │ │ └── index.tsx │ │ ├── Search │ │ │ ├── SearchForm.tsx │ │ │ └── index.tsx │ │ ├── Signin │ │ │ └── index.tsx │ │ └── common │ │ │ ├── atoms │ │ │ ├── CardAttribute.tsx │ │ │ ├── CardContent.tsx │ │ │ ├── CardDescription.tsx │ │ │ ├── CardInfo.tsx │ │ │ ├── CardSummary.tsx │ │ │ ├── CardTitle.tsx │ │ │ ├── ItemCard.tsx │ │ │ ├── LargeCoverImage.tsx │ │ │ ├── LinkButton.tsx │ │ │ ├── ListLoader.tsx │ │ │ ├── MainFrame.tsx │ │ │ ├── SmallCoverImage.tsx │ │ │ ├── Spacer.tsx │ │ │ ├── ToggleButton.tsx │ │ │ └── WideButton.tsx │ │ │ ├── card │ │ │ └── BookCard.tsx │ │ │ ├── header │ │ │ └── DividingHeader.tsx │ │ │ ├── item-tools.ts │ │ │ ├── list │ │ │ ├── BookList.tsx │ │ │ └── CalendarList.tsx │ │ │ ├── menubar │ │ │ └── NavigationBar.tsx │ │ │ └── work │ │ │ ├── AttributesField.tsx │ │ │ ├── ButtonGroup.tsx │ │ │ ├── CardGroup.tsx │ │ │ ├── WorkAuthors.tsx │ │ │ ├── WorkPlaceHolder.tsx │ │ │ ├── WorkPublishedOn.tsx │ │ │ └── WorkTitle.tsx │ ├── containers │ │ ├── Book │ │ │ └── BookMain.tsx │ │ ├── Home │ │ │ └── Calendar.tsx │ │ └── Search │ │ │ └── SearchUnit.tsx │ ├── contexts.ts │ ├── firebase-config.ts │ ├── hooks │ │ ├── use-book-search.ts │ │ ├── use-book.ts │ │ └── use-books.ts │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── paths.ts │ ├── react-app-env.d.ts │ ├── serviceWorker.ts │ ├── services │ │ └── mangarel │ │ │ ├── constants.ts │ │ │ ├── find-user.ts │ │ │ ├── models │ │ │ ├── author.ts │ │ │ ├── book.ts │ │ │ ├── feed-memo.ts │ │ │ ├── publisher.ts │ │ │ └── user.ts │ │ │ └── write-user.ts │ ├── theme.ts │ └── utils │ │ ├── env.ts │ │ ├── n-gram.spec.ts │ │ ├── n-gram.ts │ │ ├── text-processor.spec.ts │ │ ├── text-processor.ts │ │ └── timer.ts │ ├── stylelint.config.js │ ├── tsconfig.json │ └── yarn.lock ├── LICENSE ├── README.md ├── errata.md └── samples └── react-firebase-sample.pdf /01-env/mangarel-demo/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.config.js 3 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/.firebaserc.sample: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "mangarel-demo" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Dependency directories 16 | node_modules/ 17 | jspm_packages/ 18 | /.pnp 19 | .pnp.js 20 | 21 | # Testing 22 | /coverage 23 | 24 | # Production 25 | /build 26 | 27 | # TypeScript v1 declaration files 28 | typings/ 29 | 30 | # Optional npm cache directory 31 | .npm 32 | 33 | # Optional Firebase directory 34 | .firebase 35 | 36 | # Optional eslint cache 37 | .eslintcache 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | 42 | # Output of 'npm pack' 43 | *.tgz 44 | 45 | # Yarn Integrity file 46 | .yarn-integrity 47 | 48 | # IDE 49 | .idea/ 50 | .*.swp 51 | .vscode/chrome 52 | 53 | # environment variables file 54 | .env 55 | #.env.* 56 | .firebaserc 57 | 58 | # misc 59 | .DS_Store 60 | npm-debug.log* 61 | yarn-debug.log* 62 | yarn-error.log* 63 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "printWidth": 80, 4 | "semi": true, 5 | "singleQuote": true, 6 | "trailingComma": 'all', 7 | "useTabs": false, 8 | } 9 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "aaron-bond.better-comments", 4 | "CoenraadS.bracket-pair-colorizer", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "hex-ci.stylelint-plus", 8 | "jpoissonnier.vscode-styled-components", 9 | "mechatroner.rainbow-csv", 10 | "mikestead.dotenv", 11 | "oderwat.indent-rainbow", 12 | "orta.vscode-jest", 13 | "toba.vsfire", 14 | "vincaslt.highlight-matching-tag", 15 | "VisualStudioExptTeam.vscodeintellicode", 16 | "vscode-icons-team.vscode-icons" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "css.validate": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true 5 | }, 6 | "editor.tabSize": 2, 7 | "editor.wordWrap": "on", 8 | "eslint.autoFixOnSave": true, 9 | "eslint.enable": true, 10 | "eslint.packageManager": "yarn", 11 | "eslint.validate": [ 12 | { 13 | "language": "javascript", 14 | "autoFix": true 15 | }, 16 | { 17 | "language": "javascriptreact", 18 | "autoFix": true 19 | }, 20 | { 21 | "language": "typescript", 22 | "autoFix": true 23 | }, 24 | { 25 | "language": "typescriptreact", 26 | "autoFix": true 27 | } 28 | ], 29 | "stylelint.autoFixOnSave": true, 30 | "stylelint.configOverrides": { 31 | "ignoreFiles": ["node_modules/**/*.*"] 32 | }, 33 | "stylelint.enable": true, 34 | "typescript.tsdk": "node_modules/typescript/lib" 35 | } 36 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "firestore": { 3 | "rules": "firestore.rules", 4 | "indexes": "firestore.indexes.json" 5 | }, 6 | "functions": { 7 | "predeploy": "npm --prefix \"$RESOURCE_DIR\" run build" 8 | }, 9 | "hosting": { 10 | "public": "build", 11 | "ignore": [ 12 | "firebase.json", 13 | "**/.*", 14 | "**/node_modules/**" 15 | ], 16 | "rewrites": [ 17 | { 18 | "source": "**", 19 | "destination": "/index.html" 20 | } 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | // Example: 3 | // 4 | // "indexes": [ 5 | // { 6 | // "collectionGroup": "widgets", 7 | // "queryScope": "COLLECTION", 8 | // "fields": [ 9 | // { "fieldPath": "foo", "arrayConfig": "CONTAINS" }, 10 | // { "fieldPath": "bar", "mode": "DESCENDING" } 11 | // ] 12 | // }, 13 | // 14 | // "fieldOverrides": [ 15 | // { 16 | // "collectionGroup": "widgets", 17 | // "fieldPath": "baz", 18 | // "indexes": [ 19 | // { "order": "ASCENDING", "queryScope": "COLLECTION" } 20 | // ] 21 | // }, 22 | // ] 23 | // ] 24 | "indexes": [], 25 | "fieldOverrides": [] 26 | } -------------------------------------------------------------------------------- /01-env/mangarel-demo/firestore.rules: -------------------------------------------------------------------------------- 1 | service cloud.firestore { 2 | match /databases/{database}/documents { 3 | match /{document=**} { 4 | allow read, write; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/functions/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/functions/.gitignore: -------------------------------------------------------------------------------- 1 | ## Compiled JavaScript files 2 | lib/**/*.js 3 | **/*.js.map 4 | 5 | # Typescript v1 declaration files 6 | typings/ 7 | 8 | node_modules/ 9 | 10 | # Firebase Admin SDK credential 11 | *firebase-adminsdk*.json 12 | 13 | # Firebase environment variables file 14 | .runtimeconfig.json 15 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/functions/.node-version: -------------------------------------------------------------------------------- 1 | 10.17.0 2 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/functions/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | ../../.vscode/extensions.json -------------------------------------------------------------------------------- /01-env/mangarel-demo/functions/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | ../../.vscode/settings.json -------------------------------------------------------------------------------- /01-env/mangarel-demo/functions/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "typescript", 8 | "tsconfig": "tsconfig.json", 9 | "option": "watch", 10 | "problemMatcher": [ 11 | "$tsc-watch" 12 | ], 13 | "group": "build" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /01-env/mangarel-demo/functions/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as functions from 'firebase-functions'; 2 | 3 | // // Start writing Firebase Functions 4 | // // https://firebase.google.com/docs/functions/typescript 5 | // 6 | export const helloWorld = functions.https.onRequest((request, response) => { 7 | response.send('Hello from Firebase!'); 8 | }); 9 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "lib": ["dom", "esnext"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "noImplicitReturns": true, 8 | "noUnusedLocals": true, 9 | "outDir": "lib", 10 | "rootDir": "src", 11 | "removeComments": true, 12 | "resolveJsonModule": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "target": "es2017", 16 | "types": ["jest", "node"], 17 | "typeRoots": ["node_modules/@types", "../node_modules/@types"] 18 | }, 19 | "compileOnSave": true, 20 | "include": ["src"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/01-env/mangarel-demo/public/favicon.ico -------------------------------------------------------------------------------- /01-env/mangarel-demo/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/01-env/mangarel-demo/public/logo192.png -------------------------------------------------------------------------------- /01-env/mangarel-demo/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/01-env/mangarel-demo/public/logo512.png -------------------------------------------------------------------------------- /01-env/mangarel-demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .App-header { 12 | align-items: center; 13 | background-color: #282c34; 14 | color: white; 15 | display: flex; 16 | flex-direction: column; 17 | font-size: calc(10px + 2vmin); 18 | justify-content: center; 19 | min-height: 100vh; 20 | } 21 | 22 | .App-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes App-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | 31 | to { 32 | transform: rotate(360deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | 5 | const App: React.FC = () => { 6 | return ( 7 |
8 |
9 | logo 10 |

11 | Edit src/App.tsx and save to reload. 12 |

13 | 19 | Learn React 20 | 21 |
22 |
23 | ); 24 | }; 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 3 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 4 | sans-serif; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | margin: 0; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /01-env/mangarel-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react", 17 | "baseUrl": "src", 18 | "incremental": true 19 | }, 20 | "include": ["src"], 21 | "exclude": ["node_modules", "build", "scripts", "functions"] 22 | } 23 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.config.js 3 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/.firebaserc.sample: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "mangarel-demo" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Dependency directories 16 | node_modules/ 17 | jspm_packages/ 18 | /.pnp 19 | .pnp.js 20 | 21 | # Testing 22 | /coverage 23 | 24 | # Production 25 | /build 26 | 27 | # TypeScript v1 declaration files 28 | typings/ 29 | 30 | # Optional npm cache directory 31 | .npm 32 | 33 | # Optional Firebase directory 34 | .firebase 35 | 36 | # Optional eslint cache 37 | .eslintcache 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | 42 | # Output of 'npm pack' 43 | *.tgz 44 | 45 | # Yarn Integrity file 46 | .yarn-integrity 47 | 48 | # IDE 49 | .idea/ 50 | .*.swp 51 | .vscode/chrome 52 | 53 | # environment variables file 54 | .env 55 | #.env.* 56 | .firebaserc 57 | 58 | # misc 59 | .DS_Store 60 | npm-debug.log* 61 | yarn-debug.log* 62 | yarn-error.log* 63 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "printWidth": 80, 4 | "semi": true, 5 | "singleQuote": true, 6 | "trailingComma": 'all', 7 | "useTabs": false, 8 | } 9 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "aaron-bond.better-comments", 4 | "CoenraadS.bracket-pair-colorizer", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "hex-ci.stylelint-plus", 8 | "jpoissonnier.vscode-styled-components", 9 | "mechatroner.rainbow-csv", 10 | "mikestead.dotenv", 11 | "oderwat.indent-rainbow", 12 | "orta.vscode-jest", 13 | "toba.vsfire", 14 | "vincaslt.highlight-matching-tag", 15 | "VisualStudioExptTeam.vscodeintellicode", 16 | "vscode-icons-team.vscode-icons" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "css.validate": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true 5 | }, 6 | "editor.tabSize": 2, 7 | "editor.wordWrap": "on", 8 | "eslint.autoFixOnSave": true, 9 | "eslint.enable": true, 10 | "eslint.packageManager": "yarn", 11 | "eslint.validate": [ 12 | { 13 | "language": "javascript", 14 | "autoFix": true 15 | }, 16 | { 17 | "language": "javascriptreact", 18 | "autoFix": true 19 | }, 20 | { 21 | "language": "typescript", 22 | "autoFix": true 23 | }, 24 | { 25 | "language": "typescriptreact", 26 | "autoFix": true 27 | } 28 | ], 29 | "stylelint.autoFixOnSave": true, 30 | "stylelint.configOverrides": { 31 | "ignoreFiles": ["node_modules/**/*.*"] 32 | }, 33 | "stylelint.enable": true, 34 | "typescript.tsdk": "node_modules/typescript/lib" 35 | } 36 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "firestore": { 3 | "rules": "firestore.rules", 4 | "indexes": "firestore.indexes.json" 5 | }, 6 | "functions": { 7 | "predeploy": "npm --prefix \"$RESOURCE_DIR\" run build" 8 | }, 9 | "hosting": { 10 | "public": "build", 11 | "ignore": [ 12 | "firebase.json", 13 | "**/.*", 14 | "**/node_modules/**" 15 | ], 16 | "rewrites": [ 17 | { 18 | "source": "**", 19 | "destination": "/index.html" 20 | } 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | // Example: 3 | // 4 | // "indexes": [ 5 | // { 6 | // "collectionGroup": "widgets", 7 | // "queryScope": "COLLECTION", 8 | // "fields": [ 9 | // { "fieldPath": "foo", "arrayConfig": "CONTAINS" }, 10 | // { "fieldPath": "bar", "mode": "DESCENDING" } 11 | // ] 12 | // }, 13 | // 14 | // "fieldOverrides": [ 15 | // { 16 | // "collectionGroup": "widgets", 17 | // "fieldPath": "baz", 18 | // "indexes": [ 19 | // { "order": "ASCENDING", "queryScope": "COLLECTION" } 20 | // ] 21 | // }, 22 | // ] 23 | // ] 24 | "indexes": [], 25 | "fieldOverrides": [] 26 | } -------------------------------------------------------------------------------- /02-seed/mangarel-demo/firestore.rules: -------------------------------------------------------------------------------- 1 | service cloud.firestore { 2 | match /databases/{database}/documents { 3 | match /{document=**} { 4 | allow read, write; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/functions/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/functions/.gitignore: -------------------------------------------------------------------------------- 1 | ## Compiled JavaScript files 2 | lib/**/*.js 3 | **/*.js.map 4 | 5 | # Typescript v1 declaration files 6 | typings/ 7 | 8 | node_modules/ 9 | 10 | # Firebase Admin SDK credential 11 | *firebase-adminsdk*.json 12 | 13 | # Firebase environment variables file 14 | .runtimeconfig.json 15 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/functions/.node-version: -------------------------------------------------------------------------------- 1 | 10.17.0 2 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/functions/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | ../../.vscode/extensions.json -------------------------------------------------------------------------------- /02-seed/mangarel-demo/functions/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | ../../.vscode/settings.json -------------------------------------------------------------------------------- /02-seed/mangarel-demo/functions/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "typescript", 8 | "tsconfig": "tsconfig.json", 9 | "option": "watch", 10 | "problemMatcher": [ 11 | "$tsc-watch" 12 | ], 13 | "group": "build" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /02-seed/mangarel-demo/functions/src/firestore-admin/record-counter.ts: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin'; 2 | import { collectionName } from '../services/mangarel/constants'; 3 | 4 | export const addCounter = async ( 5 | db: admin.firestore.Firestore, 6 | collName: string, 7 | count = 1, 8 | ) => { 9 | const doc = db.collection(collectionName.docCounters).doc(collName); 10 | await doc.set( 11 | { 12 | count: admin.firestore.FieldValue.increment(count), 13 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 14 | }, 15 | { merge: true }, 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/functions/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as functions from 'firebase-functions'; 2 | 3 | // // Start writing Firebase Functions 4 | // // https://firebase.google.com/docs/functions/typescript 5 | // 6 | export const helloWorld = functions.https.onRequest((request, response) => { 7 | response.send('Hello from Firebase!'); 8 | }); 9 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/functions/src/services/mangarel/constants.ts: -------------------------------------------------------------------------------- 1 | export const collectionName = { 2 | authors: 'authors', 3 | books: 'books', 4 | users: 'users', 5 | publishers: 'publishers', 6 | docCounters: 'docCounters', 7 | feedMemos: 'feedMemos', 8 | } as const; 9 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/functions/src/services/mangarel/models/publisher.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | 3 | export type Publisher = { 4 | id?: string; 5 | name: string; 6 | nameReading: string | null; 7 | website: string | null; 8 | createdAt: firestore.Timestamp | null; 9 | updatedAt: firestore.Timestamp | null; 10 | }; 11 | 12 | export const blankPublisher: Publisher = { 13 | name: '', 14 | nameReading: null, 15 | website: null, 16 | createdAt: null, 17 | updatedAt: null, 18 | }; 19 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/functions/src/utils: -------------------------------------------------------------------------------- 1 | ../../src/utils -------------------------------------------------------------------------------- /02-seed/mangarel-demo/functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "lib": ["dom", "esnext"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "noImplicitReturns": true, 8 | "noUnusedLocals": true, 9 | "outDir": "lib", 10 | "rootDir": "src", 11 | "removeComments": true, 12 | "resolveJsonModule": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "target": "es2017", 16 | "types": ["jest", "node"], 17 | "typeRoots": ["node_modules/@types", "../node_modules/@types"] 18 | }, 19 | "compileOnSave": true, 20 | "include": ["src"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/02-seed/mangarel-demo/public/favicon.ico -------------------------------------------------------------------------------- /02-seed/mangarel-demo/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/02-seed/mangarel-demo/public/logo192.png -------------------------------------------------------------------------------- /02-seed/mangarel-demo/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/02-seed/mangarel-demo/public/logo512.png -------------------------------------------------------------------------------- /02-seed/mangarel-demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .App-header { 12 | align-items: center; 13 | background-color: #282c34; 14 | color: white; 15 | display: flex; 16 | flex-direction: column; 17 | font-size: calc(10px + 2vmin); 18 | justify-content: center; 19 | min-height: 100vh; 20 | } 21 | 22 | .App-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes App-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | 31 | to { 32 | transform: rotate(360deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | 5 | const App: React.FC = () => { 6 | return ( 7 |
8 |
9 | logo 10 |

11 | Edit src/App.tsx and save to reload. 12 |

13 | 19 | Learn React 20 | 21 |
22 |
23 | ); 24 | }; 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 3 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 4 | sans-serif; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | margin: 0; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /02-seed/mangarel-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react", 17 | "baseUrl": "src", 18 | "incremental": true 19 | }, 20 | "include": ["src"], 21 | "exclude": ["node_modules", "build", "scripts", "functions"] 22 | } 23 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.config.js 3 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/.firebaserc.sample: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "mangarel-demo" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Dependency directories 16 | node_modules/ 17 | jspm_packages/ 18 | /.pnp 19 | .pnp.js 20 | 21 | # Testing 22 | /coverage 23 | 24 | # Production 25 | /build 26 | 27 | # TypeScript v1 declaration files 28 | typings/ 29 | 30 | # Optional npm cache directory 31 | .npm 32 | 33 | # Optional Firebase directory 34 | .firebase 35 | 36 | # Optional eslint cache 37 | .eslintcache 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | 42 | # Output of 'npm pack' 43 | *.tgz 44 | 45 | # Yarn Integrity file 46 | .yarn-integrity 47 | 48 | # IDE 49 | .idea/ 50 | .*.swp 51 | .vscode/chrome 52 | 53 | # environment variables file 54 | .env 55 | #.env.* 56 | .firebaserc 57 | 58 | # misc 59 | .DS_Store 60 | npm-debug.log* 61 | yarn-debug.log* 62 | yarn-error.log* 63 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "printWidth": 80, 4 | "semi": true, 5 | "singleQuote": true, 6 | "trailingComma": 'all', 7 | "useTabs": false, 8 | } 9 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "aaron-bond.better-comments", 4 | "CoenraadS.bracket-pair-colorizer", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "hex-ci.stylelint-plus", 8 | "jpoissonnier.vscode-styled-components", 9 | "mechatroner.rainbow-csv", 10 | "mikestead.dotenv", 11 | "oderwat.indent-rainbow", 12 | "orta.vscode-jest", 13 | "toba.vsfire", 14 | "vincaslt.highlight-matching-tag", 15 | "VisualStudioExptTeam.vscodeintellicode", 16 | "vscode-icons-team.vscode-icons" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "css.validate": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true 5 | }, 6 | "editor.tabSize": 2, 7 | "editor.wordWrap": "on", 8 | "eslint.autoFixOnSave": true, 9 | "eslint.enable": true, 10 | "eslint.packageManager": "yarn", 11 | "eslint.validate": [ 12 | { 13 | "language": "javascript", 14 | "autoFix": true 15 | }, 16 | { 17 | "language": "javascriptreact", 18 | "autoFix": true 19 | }, 20 | { 21 | "language": "typescript", 22 | "autoFix": true 23 | }, 24 | { 25 | "language": "typescriptreact", 26 | "autoFix": true 27 | } 28 | ], 29 | "stylelint.autoFixOnSave": true, 30 | "stylelint.configOverrides": { 31 | "ignoreFiles": ["node_modules/**/*.*"] 32 | }, 33 | "stylelint.enable": true, 34 | "typescript.tsdk": "node_modules/typescript/lib" 35 | } 36 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "firestore": { 3 | "rules": "firestore.rules", 4 | "indexes": "firestore.indexes.json" 5 | }, 6 | "functions": { 7 | "predeploy": "npm --prefix \"$RESOURCE_DIR\" run build" 8 | }, 9 | "hosting": { 10 | "public": "build", 11 | "ignore": [ 12 | "firebase.json", 13 | "**/.*", 14 | "**/node_modules/**" 15 | ], 16 | "rewrites": [ 17 | { 18 | "source": "**", 19 | "destination": "/index.html" 20 | } 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | // Example: 3 | // 4 | // "indexes": [ 5 | // { 6 | // "collectionGroup": "widgets", 7 | // "queryScope": "COLLECTION", 8 | // "fields": [ 9 | // { "fieldPath": "foo", "arrayConfig": "CONTAINS" }, 10 | // { "fieldPath": "bar", "mode": "DESCENDING" } 11 | // ] 12 | // }, 13 | // 14 | // "fieldOverrides": [ 15 | // { 16 | // "collectionGroup": "widgets", 17 | // "fieldPath": "baz", 18 | // "indexes": [ 19 | // { "order": "ASCENDING", "queryScope": "COLLECTION" } 20 | // ] 21 | // }, 22 | // ] 23 | // ] 24 | "indexes": [], 25 | "fieldOverrides": [] 26 | } -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/firestore.rules: -------------------------------------------------------------------------------- 1 | service cloud.firestore { 2 | match /databases/{database}/documents { 3 | match /{document=**} { 4 | allow read, write; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/functions/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/functions/.gitignore: -------------------------------------------------------------------------------- 1 | ## Compiled JavaScript files 2 | lib/**/*.js 3 | **/*.js.map 4 | 5 | # Typescript v1 declaration files 6 | typings/ 7 | 8 | node_modules/ 9 | 10 | # Firebase Admin SDK credential 11 | *firebase-adminsdk*.json 12 | 13 | # Firebase environment variables file 14 | .runtimeconfig.json 15 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/functions/.node-version: -------------------------------------------------------------------------------- 1 | 10.17.0 2 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/functions/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | ../../.vscode/extensions.json -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/functions/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | ../../.vscode/settings.json -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/functions/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "typescript", 8 | "tsconfig": "tsconfig.json", 9 | "option": "watch", 10 | "problemMatcher": [ 11 | "$tsc-watch" 12 | ], 13 | "group": "build" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/functions/src/firestore-admin/record-counter.ts: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin'; 2 | import { collectionName } from '../services/mangarel/constants'; 3 | 4 | export const addCounter = async ( 5 | db: admin.firestore.Firestore, 6 | collName: string, 7 | count = 1, 8 | ) => { 9 | const doc = db.collection(collectionName.docCounters).doc(collName); 10 | await doc.set( 11 | { 12 | count: admin.firestore.FieldValue.increment(count), 13 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 14 | }, 15 | { merge: true }, 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/functions/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as functions from 'firebase-functions'; 2 | import admin from 'firebase-admin'; 3 | 4 | import { collectionName } from './services/mangarel/constants'; 5 | 6 | admin.initializeApp(); 7 | 8 | export const publishers = functions 9 | .region('asia-northeast1') 10 | .https.onRequest(async (req, res) => { 11 | const snap = await admin 12 | .firestore() 13 | .collection(collectionName.publishers) 14 | .get(); 15 | const data = snap.docs.map(doc => doc.data()); 16 | res.send({ data }); 17 | }); 18 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/functions/src/services/mangarel/constants.ts: -------------------------------------------------------------------------------- 1 | export const collectionName = { 2 | authors: 'authors', 3 | books: 'books', 4 | users: 'users', 5 | publishers: 'publishers', 6 | docCounters: 'docCounters', 7 | feedMemos: 'feedMemos', 8 | } as const; 9 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/functions/src/services/mangarel/models/publisher.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | 3 | export type Publisher = { 4 | id?: string; 5 | name: string; 6 | nameReading: string | null; 7 | website: string | null; 8 | createdAt: firestore.Timestamp | null; 9 | updatedAt: firestore.Timestamp | null; 10 | }; 11 | 12 | export const blankPublisher: Publisher = { 13 | name: '', 14 | nameReading: null, 15 | website: null, 16 | createdAt: null, 17 | updatedAt: null, 18 | }; 19 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "lib": ["dom", "esnext"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "noImplicitReturns": true, 8 | "noUnusedLocals": true, 9 | "outDir": "lib", 10 | "rootDir": "src", 11 | "removeComments": true, 12 | "resolveJsonModule": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "target": "es2017", 16 | "types": ["jest", "node"], 17 | "typeRoots": ["node_modules/@types", "../node_modules/@types"] 18 | }, 19 | "compileOnSave": true, 20 | "include": ["src"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/03-functions/01-publishers/mangarel-demo/public/favicon.ico -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/03-functions/01-publishers/mangarel-demo/public/logo192.png -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/03-functions/01-publishers/mangarel-demo/public/logo512.png -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .App-header { 12 | align-items: center; 13 | background-color: #282c34; 14 | color: white; 15 | display: flex; 16 | flex-direction: column; 17 | font-size: calc(10px + 2vmin); 18 | justify-content: center; 19 | min-height: 100vh; 20 | } 21 | 22 | .App-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes App-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | 31 | to { 32 | transform: rotate(360deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | 5 | const App: React.FC = () => { 6 | return ( 7 |
8 |
9 | logo 10 |

11 | Edit src/App.tsx and save to reload. 12 |

13 | 19 | Learn React 20 | 21 |
22 |
23 | ); 24 | }; 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 3 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 4 | sans-serif; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | margin: 0; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /03-functions/01-publishers/mangarel-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react", 17 | "baseUrl": "src", 18 | "incremental": true 19 | }, 20 | "include": ["src"], 21 | "exclude": ["node_modules", "build", "scripts", "functions"] 22 | } 23 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.config.js 3 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/.firebaserc.sample: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "mangarel-demo" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Dependency directories 16 | node_modules/ 17 | jspm_packages/ 18 | /.pnp 19 | .pnp.js 20 | 21 | # Testing 22 | /coverage 23 | 24 | # Production 25 | /build 26 | 27 | # TypeScript v1 declaration files 28 | typings/ 29 | 30 | # Optional npm cache directory 31 | .npm 32 | 33 | # Optional Firebase directory 34 | .firebase 35 | 36 | # Optional eslint cache 37 | .eslintcache 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | 42 | # Output of 'npm pack' 43 | *.tgz 44 | 45 | # Yarn Integrity file 46 | .yarn-integrity 47 | 48 | # IDE 49 | .idea/ 50 | .*.swp 51 | .vscode/chrome 52 | 53 | # environment variables file 54 | .env 55 | #.env.* 56 | .firebaserc 57 | 58 | # misc 59 | .DS_Store 60 | npm-debug.log* 61 | yarn-debug.log* 62 | yarn-error.log* 63 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "printWidth": 80, 4 | "semi": true, 5 | "singleQuote": true, 6 | "trailingComma": 'all', 7 | "useTabs": false, 8 | } 9 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "aaron-bond.better-comments", 4 | "CoenraadS.bracket-pair-colorizer", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "hex-ci.stylelint-plus", 8 | "jpoissonnier.vscode-styled-components", 9 | "mechatroner.rainbow-csv", 10 | "mikestead.dotenv", 11 | "oderwat.indent-rainbow", 12 | "orta.vscode-jest", 13 | "toba.vsfire", 14 | "vincaslt.highlight-matching-tag", 15 | "VisualStudioExptTeam.vscodeintellicode", 16 | "vscode-icons-team.vscode-icons" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "css.validate": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true 5 | }, 6 | "editor.tabSize": 2, 7 | "editor.wordWrap": "on", 8 | "eslint.autoFixOnSave": true, 9 | "eslint.enable": true, 10 | "eslint.packageManager": "yarn", 11 | "eslint.validate": [ 12 | { 13 | "language": "javascript", 14 | "autoFix": true 15 | }, 16 | { 17 | "language": "javascriptreact", 18 | "autoFix": true 19 | }, 20 | { 21 | "language": "typescript", 22 | "autoFix": true 23 | }, 24 | { 25 | "language": "typescriptreact", 26 | "autoFix": true 27 | } 28 | ], 29 | "stylelint.autoFixOnSave": true, 30 | "stylelint.configOverrides": { 31 | "ignoreFiles": ["node_modules/**/*.*"] 32 | }, 33 | "stylelint.enable": true, 34 | "typescript.tsdk": "node_modules/typescript/lib" 35 | } 36 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "firestore": { 3 | "rules": "firestore.rules", 4 | "indexes": "firestore.indexes.json" 5 | }, 6 | "functions": { 7 | "predeploy": "npm --prefix \"$RESOURCE_DIR\" run build" 8 | }, 9 | "hosting": { 10 | "public": "build", 11 | "ignore": [ 12 | "firebase.json", 13 | "**/.*", 14 | "**/node_modules/**" 15 | ], 16 | "rewrites": [ 17 | { 18 | "source": "**", 19 | "destination": "/index.html" 20 | } 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | // Example: 3 | // 4 | // "indexes": [ 5 | // { 6 | // "collectionGroup": "widgets", 7 | // "queryScope": "COLLECTION", 8 | // "fields": [ 9 | // { "fieldPath": "foo", "arrayConfig": "CONTAINS" }, 10 | // { "fieldPath": "bar", "mode": "DESCENDING" } 11 | // ] 12 | // }, 13 | // 14 | // "fieldOverrides": [ 15 | // { 16 | // "collectionGroup": "widgets", 17 | // "fieldPath": "baz", 18 | // "indexes": [ 19 | // { "order": "ASCENDING", "queryScope": "COLLECTION" } 20 | // ] 21 | // }, 22 | // ] 23 | // ] 24 | "indexes": [], 25 | "fieldOverrides": [] 26 | } -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/firestore.rules: -------------------------------------------------------------------------------- 1 | service cloud.firestore { 2 | match /databases/{database}/documents { 3 | match /{document=**} { 4 | allow read, write; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/functions/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/functions/.gitignore: -------------------------------------------------------------------------------- 1 | ## Compiled JavaScript files 2 | lib/**/*.js 3 | **/*.js.map 4 | 5 | # Typescript v1 declaration files 6 | typings/ 7 | 8 | node_modules/ 9 | 10 | # Firebase Admin SDK credential 11 | *firebase-adminsdk*.json 12 | 13 | # Firebase environment variables file 14 | .runtimeconfig.json 15 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/functions/.node-version: -------------------------------------------------------------------------------- 1 | 10.17.0 2 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/functions/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | ../../.vscode/extensions.json -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/functions/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | ../../.vscode/settings.json -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/functions/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "typescript", 8 | "tsconfig": "tsconfig.json", 9 | "option": "watch", 10 | "problemMatcher": [ 11 | "$tsc-watch" 12 | ], 13 | "group": "build" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/functions/src/firestore-admin/publisher.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-loop-func */ 2 | import admin from 'firebase-admin'; 3 | 4 | import { collectionName } from '../services/mangarel/constants'; 5 | import { Publisher } from '../services/mangarel/models/publisher'; 6 | 7 | export const findPublisher = async ( 8 | db: admin.firestore.Firestore, 9 | id: string, 10 | ) => { 11 | const doc = await db 12 | .collection(collectionName.publishers) 13 | .doc(id) 14 | .get(); 15 | const publisher = doc.data() as Publisher; 16 | 17 | return { ...publisher, id: doc.id }; 18 | }; 19 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/functions/src/firestore-admin/record-counter.ts: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin'; 2 | import { collectionName } from '../services/mangarel/constants'; 3 | 4 | export const addCounter = async ( 5 | db: admin.firestore.Firestore, 6 | collName: string, 7 | count = 1, 8 | ) => { 9 | const doc = db.collection(collectionName.docCounters).doc(collName); 10 | await doc.set( 11 | { 12 | count: admin.firestore.FieldValue.increment(count), 13 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 14 | }, 15 | { merge: true }, 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/functions/src/services/mangarel/constants.ts: -------------------------------------------------------------------------------- 1 | export const collectionName = { 2 | authors: 'authors', 3 | books: 'books', 4 | users: 'users', 5 | publishers: 'publishers', 6 | docCounters: 'docCounters', 7 | feedMemos: 'feedMemos', 8 | } as const; 9 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/functions/src/services/mangarel/models/author.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | 3 | export type Author = { 4 | id?: string; 5 | name: string; 6 | nameReading: string | null; 7 | variation: string; 8 | createdAt: firestore.Timestamp | null; 9 | updatedAt: firestore.Timestamp | null; 10 | }; 11 | 12 | export const blankAuthor: Author = { 13 | name: '', 14 | nameReading: null, 15 | variation: '', 16 | createdAt: null, 17 | updatedAt: null, 18 | }; 19 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/functions/src/services/mangarel/models/book.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | import { Author } from './author'; 3 | import { Publisher } from './publisher'; 4 | 5 | export type Book = { 6 | id?: string; 7 | title: string; 8 | titleReading: string | null; 9 | publisherId: string; 10 | publisher: Publisher | null; 11 | authorIds: string[]; 12 | authors: Author[]; 13 | isbn: string; 14 | rbCode: string; 15 | hasImage: boolean; 16 | publishedOn: firestore.Timestamp | null; 17 | createdAt: firestore.Timestamp | null; 18 | updatedAt: firestore.Timestamp | null; 19 | }; 20 | 21 | export const blankBook: Book = { 22 | title: '', 23 | titleReading: null, 24 | publisherId: '', 25 | publisher: null, 26 | authorIds: [], 27 | authors: [], 28 | isbn: '', 29 | rbCode: '', 30 | hasImage: false, 31 | publishedOn: null, 32 | createdAt: null, 33 | updatedAt: null, 34 | }; 35 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/functions/src/services/mangarel/models/feed-memo.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | 3 | export type FeedMemo = { 4 | id?: string; 5 | title: string | null; 6 | author: string | null; 7 | publisher: string | null; 8 | releaseDate: string | null; 9 | isbn: string | null; 10 | fetchedAt: firestore.Timestamp | null; 11 | createdAt: firestore.Timestamp | null; 12 | }; 13 | 14 | export const blankFeedMemo: FeedMemo = { 15 | title: null, 16 | author: null, 17 | publisher: null, 18 | releaseDate: null, 19 | isbn: null, 20 | fetchedAt: null, 21 | createdAt: null, 22 | }; 23 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/functions/src/services/mangarel/models/publisher.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | 3 | export type Publisher = { 4 | id?: string; 5 | name: string; 6 | nameReading: string | null; 7 | website: string | null; 8 | createdAt: firestore.Timestamp | null; 9 | updatedAt: firestore.Timestamp | null; 10 | }; 11 | 12 | export const blankPublisher: Publisher = { 13 | name: '', 14 | nameReading: null, 15 | website: null, 16 | createdAt: null, 17 | updatedAt: null, 18 | }; 19 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/functions/src/services/rakuten/models/book-item.ts: -------------------------------------------------------------------------------- 1 | export type BookItem = { 2 | title: string; 3 | titleKana: string; 4 | subTitle: string; 5 | subTitleKana: string; 6 | seriesName: string; 7 | seriesNameKana: string; 8 | contents: string; 9 | author: string; 10 | authorKana: string; 11 | publisherName: string; 12 | size: string; 13 | isbn: string; 14 | itemCaption: string; 15 | salesDate: string; 16 | itemPrice: number; 17 | listPrice: number; 18 | discountRate: number; 19 | discountPrice: number; 20 | itemUrl: string; 21 | affiliateUrl: string; 22 | smallImageUrl: string; 23 | mediumImageUrl: string; 24 | largeImageUrl: string; 25 | chirayomiUrl: string; 26 | availability: string; 27 | postageFlag: number; 28 | limitedFlag: number; 29 | reviewCount: number; 30 | reviewAverage: string; 31 | booksGenreId: string; 32 | }; 33 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/functions/src/utils/env.ts: -------------------------------------------------------------------------------- 1 | export const isDevelopment = () => (process.env.node || '').includes('/Users/'); 2 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/functions/src/utils/timer.ts: -------------------------------------------------------------------------------- 1 | export const sleep = (msec: number) => 2 | new Promise(resolve => setTimeout(() => resolve(), msec)); 3 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "lib": ["dom", "esnext"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "noImplicitReturns": true, 8 | "noUnusedLocals": true, 9 | "outDir": "lib", 10 | "rootDir": "src", 11 | "removeComments": true, 12 | "resolveJsonModule": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "target": "es2017", 16 | "types": ["jest", "node"], 17 | "typeRoots": ["node_modules/@types", "../node_modules/@types"] 18 | }, 19 | "compileOnSave": true, 20 | "include": ["src"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/03-functions/02-crawler/mangarel-demo/public/favicon.ico -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/03-functions/02-crawler/mangarel-demo/public/logo192.png -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/03-functions/02-crawler/mangarel-demo/public/logo512.png -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .App-header { 12 | align-items: center; 13 | background-color: #282c34; 14 | color: white; 15 | display: flex; 16 | flex-direction: column; 17 | font-size: calc(10px + 2vmin); 18 | justify-content: center; 19 | min-height: 100vh; 20 | } 21 | 22 | .App-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes App-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | 31 | to { 32 | transform: rotate(360deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | 5 | const App: React.FC = () => { 6 | return ( 7 |
8 |
9 | logo 10 |

11 | Edit src/App.tsx and save to reload. 12 |

13 | 19 | Learn React 20 | 21 |
22 |
23 | ); 24 | }; 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 3 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 4 | sans-serif; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | margin: 0; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /03-functions/02-crawler/mangarel-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react", 17 | "baseUrl": "src", 18 | "incremental": true 19 | }, 20 | "include": ["src"], 21 | "exclude": ["node_modules", "build", "scripts", "functions"] 22 | } 23 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.config.js 3 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/.firebaserc.sample: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "mangarel-demo" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Dependency directories 16 | node_modules/ 17 | jspm_packages/ 18 | /.pnp 19 | .pnp.js 20 | 21 | # Testing 22 | /coverage 23 | 24 | # Production 25 | /build 26 | 27 | # TypeScript v1 declaration files 28 | typings/ 29 | 30 | # Optional npm cache directory 31 | .npm 32 | 33 | # Optional Firebase directory 34 | .firebase 35 | 36 | # Optional eslint cache 37 | .eslintcache 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | 42 | # Output of 'npm pack' 43 | *.tgz 44 | 45 | # Yarn Integrity file 46 | .yarn-integrity 47 | 48 | # IDE 49 | .idea/ 50 | .*.swp 51 | .vscode/chrome 52 | 53 | # environment variables file 54 | .env 55 | #.env.* 56 | .firebaserc 57 | 58 | # misc 59 | .DS_Store 60 | npm-debug.log* 61 | yarn-debug.log* 62 | yarn-error.log* 63 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "printWidth": 80, 4 | "semi": true, 5 | "singleQuote": true, 6 | "trailingComma": 'all', 7 | "useTabs": false, 8 | } 9 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "aaron-bond.better-comments", 4 | "CoenraadS.bracket-pair-colorizer", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "hex-ci.stylelint-plus", 8 | "jpoissonnier.vscode-styled-components", 9 | "mechatroner.rainbow-csv", 10 | "mikestead.dotenv", 11 | "oderwat.indent-rainbow", 12 | "orta.vscode-jest", 13 | "toba.vsfire", 14 | "vincaslt.highlight-matching-tag", 15 | "VisualStudioExptTeam.vscodeintellicode", 16 | "vscode-icons-team.vscode-icons" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "css.validate": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true 5 | }, 6 | "editor.tabSize": 2, 7 | "editor.wordWrap": "on", 8 | "eslint.autoFixOnSave": true, 9 | "eslint.enable": true, 10 | "eslint.packageManager": "yarn", 11 | "eslint.validate": [ 12 | { 13 | "language": "javascript", 14 | "autoFix": true 15 | }, 16 | { 17 | "language": "javascriptreact", 18 | "autoFix": true 19 | }, 20 | { 21 | "language": "typescript", 22 | "autoFix": true 23 | }, 24 | { 25 | "language": "typescriptreact", 26 | "autoFix": true 27 | } 28 | ], 29 | "stylelint.autoFixOnSave": true, 30 | "stylelint.configOverrides": { 31 | "ignoreFiles": ["node_modules/**/*.*"] 32 | }, 33 | "stylelint.enable": true, 34 | "typescript.tsdk": "node_modules/typescript/lib" 35 | } 36 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "firestore": { 3 | "rules": "firestore.rules", 4 | "indexes": "firestore.indexes.json" 5 | }, 6 | "functions": { 7 | "predeploy": "npm --prefix \"$RESOURCE_DIR\" run build" 8 | }, 9 | "hosting": { 10 | "public": "build", 11 | "ignore": [ 12 | "firebase.json", 13 | "**/.*", 14 | "**/node_modules/**" 15 | ], 16 | "rewrites": [ 17 | { 18 | "source": "**", 19 | "destination": "/index.html" 20 | } 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | // Example: 3 | // 4 | // "indexes": [ 5 | // { 6 | // "collectionGroup": "widgets", 7 | // "queryScope": "COLLECTION", 8 | // "fields": [ 9 | // { "fieldPath": "foo", "arrayConfig": "CONTAINS" }, 10 | // { "fieldPath": "bar", "mode": "DESCENDING" } 11 | // ] 12 | // }, 13 | // 14 | // "fieldOverrides": [ 15 | // { 16 | // "collectionGroup": "widgets", 17 | // "fieldPath": "baz", 18 | // "indexes": [ 19 | // { "order": "ASCENDING", "queryScope": "COLLECTION" } 20 | // ] 21 | // }, 22 | // ] 23 | // ] 24 | "indexes": [], 25 | "fieldOverrides": [] 26 | } -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/firestore.rules: -------------------------------------------------------------------------------- 1 | service cloud.firestore { 2 | match /databases/{database}/documents { 3 | match /{document=**} { 4 | allow read, write; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/functions/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/functions/.gitignore: -------------------------------------------------------------------------------- 1 | ## Compiled JavaScript files 2 | lib/**/*.js 3 | **/*.js.map 4 | 5 | # Typescript v1 declaration files 6 | typings/ 7 | 8 | node_modules/ 9 | 10 | # Firebase Admin SDK credential 11 | *firebase-adminsdk*.json 12 | 13 | # Firebase environment variables file 14 | .runtimeconfig.json 15 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/functions/.node-version: -------------------------------------------------------------------------------- 1 | 10.17.0 2 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/functions/.runtimeconfig.sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "rakuten": { 3 | "app_id": "xxxxxxxxxxxxxxxxxxx" 4 | }, 5 | "locale": { 6 | "timezone": "Asia/Tokyo", 7 | "region": "asia-northeast1" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/functions/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | ../../.vscode/extensions.json -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/functions/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | ../../.vscode/settings.json -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/functions/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "typescript", 8 | "tsconfig": "tsconfig.json", 9 | "option": "watch", 10 | "problemMatcher": [ 11 | "$tsc-watch" 12 | ], 13 | "group": "build" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/functions/src/firestore-admin/publisher.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-loop-func */ 2 | import admin from 'firebase-admin'; 3 | 4 | import { collectionName } from '../services/mangarel/constants'; 5 | import { Publisher } from '../services/mangarel/models/publisher'; 6 | 7 | export const findPublisher = async ( 8 | db: admin.firestore.Firestore, 9 | id: string, 10 | ) => { 11 | const doc = await db 12 | .collection(collectionName.publishers) 13 | .doc(id) 14 | .get(); 15 | const publisher = doc.data() as Publisher; 16 | 17 | return { ...publisher, id: doc.id }; 18 | }; 19 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/functions/src/firestore-admin/record-counter.ts: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin'; 2 | import { collectionName } from '../services/mangarel/constants'; 3 | 4 | export const addCounter = async ( 5 | db: admin.firestore.Firestore, 6 | collName: string, 7 | count = 1, 8 | ) => { 9 | const doc = db.collection(collectionName.docCounters).doc(collName); 10 | await doc.set( 11 | { 12 | count: admin.firestore.FieldValue.increment(count), 13 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 14 | }, 15 | { merge: true }, 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/functions/src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable import/no-dynamic-require */ 3 | import admin from 'firebase-admin'; 4 | import forEach from 'lodash/forEach'; 5 | import { isDevelopment } from './utils/env'; 6 | 7 | admin.initializeApp(); 8 | 9 | const functionMap = { 10 | fetchCalendar: './fetch-calendar', 11 | registerBooks: './register-books', 12 | }; 13 | 14 | const devFunctionMap = { 15 | publishers: './publishers', 16 | }; 17 | 18 | const loadFunctions = (fnMap: typeof functionMap) => { 19 | forEach(fnMap, (path, functionName) => { 20 | if ( 21 | !process.env.FUNCTION_TARGET || 22 | process.env.FUNCTION_TARGET === functionName 23 | ) { 24 | module.exports[functionName] = require(path); 25 | } 26 | }); 27 | }; 28 | 29 | const fnMap = isDevelopment() 30 | ? { ...functionMap, ...devFunctionMap } 31 | : functionMap; 32 | 33 | loadFunctions(fnMap); 34 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/functions/src/publishers.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import * as functions from 'firebase-functions'; 3 | import admin from 'firebase-admin'; 4 | 5 | import { collectionName } from './services/mangarel/constants'; 6 | 7 | module.exports = functions 8 | .region('asia-northeast1') 9 | .https.onRequest(async (req, res) => { 10 | const snap = await admin 11 | .firestore() 12 | .collection(collectionName.publishers) 13 | .get(); 14 | const data = snap.docs.map(doc => doc.data()); 15 | res.send({ data }); 16 | }); 17 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/functions/src/services/mangarel: -------------------------------------------------------------------------------- 1 | ../../../src/services/mangarel -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/functions/src/services/rakuten/models/book-item.ts: -------------------------------------------------------------------------------- 1 | export type BookItem = { 2 | title: string; 3 | titleKana: string; 4 | subTitle: string; 5 | subTitleKana: string; 6 | seriesName: string; 7 | seriesNameKana: string; 8 | contents: string; 9 | author: string; 10 | authorKana: string; 11 | publisherName: string; 12 | size: string; 13 | isbn: string; 14 | itemCaption: string; 15 | salesDate: string; 16 | itemPrice: number; 17 | listPrice: number; 18 | discountRate: number; 19 | discountPrice: number; 20 | itemUrl: string; 21 | affiliateUrl: string; 22 | smallImageUrl: string; 23 | mediumImageUrl: string; 24 | largeImageUrl: string; 25 | chirayomiUrl: string; 26 | availability: string; 27 | postageFlag: number; 28 | limitedFlag: number; 29 | reviewCount: number; 30 | reviewAverage: string; 31 | booksGenreId: string; 32 | }; 33 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/functions/src/utils: -------------------------------------------------------------------------------- 1 | ../../src/utils -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "lib": ["dom", "esnext"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "noImplicitReturns": true, 8 | "noUnusedLocals": true, 9 | "outDir": "lib", 10 | "rootDir": "src", 11 | "removeComments": true, 12 | "resolveJsonModule": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "target": "es2017", 16 | "types": ["jest", "node"], 17 | "typeRoots": ["node_modules/@types", "../node_modules/@types"] 18 | }, 19 | "compileOnSave": true, 20 | "include": ["src"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/03-functions/04-advanced/mangarel-demo/public/favicon.ico -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/03-functions/04-advanced/mangarel-demo/public/logo192.png -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/03-functions/04-advanced/mangarel-demo/public/logo512.png -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .App-header { 12 | align-items: center; 13 | background-color: #282c34; 14 | color: white; 15 | display: flex; 16 | flex-direction: column; 17 | font-size: calc(10px + 2vmin); 18 | justify-content: center; 19 | min-height: 100vh; 20 | } 21 | 22 | .App-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes App-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | 31 | to { 32 | transform: rotate(360deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | 5 | const App: React.FC = () => { 6 | return ( 7 |
8 |
9 | logo 10 |

11 | Edit src/App.tsx and save to reload. 12 |

13 | 19 | Learn React 20 | 21 |
22 |
23 | ); 24 | }; 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 3 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 4 | sans-serif; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | margin: 0; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/src/services/mangarel/constants.ts: -------------------------------------------------------------------------------- 1 | export const collectionName = { 2 | authors: 'authors', 3 | books: 'books', 4 | users: 'users', 5 | publishers: 'publishers', 6 | docCounters: 'docCounters', 7 | feedMemos: 'feedMemos', 8 | } as const; 9 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/src/services/mangarel/models/author.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | 3 | export type Author = { 4 | id?: string; 5 | name: string; 6 | nameReading: string | null; 7 | variation: string; 8 | createdAt: firestore.Timestamp | null; 9 | updatedAt: firestore.Timestamp | null; 10 | }; 11 | 12 | export const blankAuthor: Author = { 13 | name: '', 14 | nameReading: null, 15 | variation: '', 16 | createdAt: null, 17 | updatedAt: null, 18 | }; 19 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/src/services/mangarel/models/book.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | import { Author } from './author'; 3 | import { Publisher } from './publisher'; 4 | 5 | export type Book = { 6 | id?: string; 7 | title: string; 8 | titleReading: string | null; 9 | publisherId: string; 10 | publisher: Publisher | null; 11 | authorIds: string[]; 12 | authors: Author[]; 13 | isbn: string; 14 | rbCode: string; 15 | hasImage: boolean; 16 | publishedOn: firestore.Timestamp | null; 17 | createdAt: firestore.Timestamp | null; 18 | updatedAt: firestore.Timestamp | null; 19 | }; 20 | 21 | export const blankBook: Book = { 22 | title: '', 23 | titleReading: null, 24 | publisherId: '', 25 | publisher: null, 26 | authorIds: [], 27 | authors: [], 28 | isbn: '', 29 | rbCode: '', 30 | hasImage: false, 31 | publishedOn: null, 32 | createdAt: null, 33 | updatedAt: null, 34 | }; 35 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/src/services/mangarel/models/feed-memo.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | 3 | export type FeedMemo = { 4 | id?: string; 5 | title: string | null; 6 | author: string | null; 7 | publisher: string | null; 8 | releaseDate: string | null; 9 | isbn: string | null; 10 | fetchedAt: firestore.Timestamp | null; 11 | createdAt: firestore.Timestamp | null; 12 | }; 13 | 14 | export const blankFeedMemo: FeedMemo = { 15 | title: null, 16 | author: null, 17 | publisher: null, 18 | releaseDate: null, 19 | isbn: null, 20 | fetchedAt: null, 21 | createdAt: null, 22 | }; 23 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/src/services/mangarel/models/publisher.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | 3 | export type Publisher = { 4 | id?: string; 5 | name: string; 6 | nameReading: string | null; 7 | website: string | null; 8 | createdAt: firestore.Timestamp | null; 9 | updatedAt: firestore.Timestamp | null; 10 | }; 11 | 12 | export const blankPublisher: Publisher = { 13 | name: '', 14 | nameReading: null, 15 | website: null, 16 | createdAt: null, 17 | updatedAt: null, 18 | }; 19 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/src/utils/env.ts: -------------------------------------------------------------------------------- 1 | export const isDevelopment = () => (process.env.node || '').includes('nodenv'); 2 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/src/utils/timer.ts: -------------------------------------------------------------------------------- 1 | export const sleep = (msec: number) => 2 | new Promise(resolve => setTimeout(() => resolve(), msec)); 3 | -------------------------------------------------------------------------------- /03-functions/04-advanced/mangarel-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react", 17 | "baseUrl": "src", 18 | "incremental": true 19 | }, 20 | "include": ["src"], 21 | "exclude": ["node_modules", "build", "scripts", "functions"] 22 | } 23 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.config.js 3 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/.firebaserc.sample: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "mangarel-demo" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Dependency directories 16 | node_modules/ 17 | jspm_packages/ 18 | /.pnp 19 | .pnp.js 20 | 21 | # Testing 22 | /coverage 23 | 24 | # Production 25 | /build 26 | 27 | # TypeScript v1 declaration files 28 | typings/ 29 | 30 | # Optional npm cache directory 31 | .npm 32 | 33 | # Optional Firebase directory 34 | .firebase 35 | 36 | # Optional eslint cache 37 | .eslintcache 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | 42 | # Output of 'npm pack' 43 | *.tgz 44 | 45 | # Yarn Integrity file 46 | .yarn-integrity 47 | 48 | # IDE 49 | .idea/ 50 | .*.swp 51 | .vscode/chrome 52 | 53 | # environment variables file 54 | .env 55 | #.env.* 56 | .firebaserc 57 | 58 | # misc 59 | .DS_Store 60 | npm-debug.log* 61 | yarn-debug.log* 62 | yarn-error.log* 63 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "printWidth": 80, 4 | "semi": true, 5 | "singleQuote": true, 6 | "trailingComma": 'all', 7 | "useTabs": false, 8 | } 9 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "aaron-bond.better-comments", 4 | "CoenraadS.bracket-pair-colorizer", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "hex-ci.stylelint-plus", 8 | "jpoissonnier.vscode-styled-components", 9 | "mechatroner.rainbow-csv", 10 | "mikestead.dotenv", 11 | "oderwat.indent-rainbow", 12 | "orta.vscode-jest", 13 | "toba.vsfire", 14 | "vincaslt.highlight-matching-tag", 15 | "VisualStudioExptTeam.vscodeintellicode", 16 | "vscode-icons-team.vscode-icons" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "css.validate": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true 5 | }, 6 | "editor.tabSize": 2, 7 | "editor.wordWrap": "on", 8 | "eslint.autoFixOnSave": true, 9 | "eslint.enable": true, 10 | "eslint.packageManager": "yarn", 11 | "eslint.validate": [ 12 | { 13 | "language": "javascript", 14 | "autoFix": true 15 | }, 16 | { 17 | "language": "javascriptreact", 18 | "autoFix": true 19 | }, 20 | { 21 | "language": "typescript", 22 | "autoFix": true 23 | }, 24 | { 25 | "language": "typescriptreact", 26 | "autoFix": true 27 | } 28 | ], 29 | "stylelint.autoFixOnSave": true, 30 | "stylelint.configOverrides": { 31 | "ignoreFiles": ["node_modules/**/*.*"] 32 | }, 33 | "stylelint.enable": true, 34 | "typescript.tsdk": "node_modules/typescript/lib" 35 | } 36 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "firestore": { 3 | "rules": "firestore.rules", 4 | "indexes": "firestore.indexes.json" 5 | }, 6 | "functions": { 7 | "predeploy": "npm --prefix \"$RESOURCE_DIR\" run build" 8 | }, 9 | "hosting": { 10 | "public": "build", 11 | "ignore": [ 12 | "firebase.json", 13 | "**/.*", 14 | "**/node_modules/**" 15 | ], 16 | "rewrites": [ 17 | { 18 | "source": "**", 19 | "destination": "/index.html" 20 | } 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | "indexes": [ 3 | { 4 | "collectionGroup": "feedMemos", 5 | "queryScope": "COLLECTION", 6 | "fields": [ 7 | { 8 | "fieldPath": "isbn", 9 | "order": "ASCENDING" 10 | }, 11 | { 12 | "fieldPath": "fetchedAt", 13 | "order": "ASCENDING" 14 | } 15 | ] 16 | } 17 | ], 18 | "fieldOverrides": [] 19 | } 20 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/firestore.rules: -------------------------------------------------------------------------------- 1 | service cloud.firestore { 2 | match /databases/{database}/documents { 3 | match /{document=**} { 4 | allow read, write; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/functions/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/functions/.gitignore: -------------------------------------------------------------------------------- 1 | ## Compiled JavaScript files 2 | lib/**/*.js 3 | **/*.js.map 4 | 5 | # Typescript v1 declaration files 6 | typings/ 7 | 8 | node_modules/ 9 | 10 | # Firebase Admin SDK credential 11 | *firebase-adminsdk*.json 12 | 13 | # Firebase environment variables file 14 | .runtimeconfig.json 15 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/functions/.node-version: -------------------------------------------------------------------------------- 1 | 10.17.0 2 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/functions/.runtimeconfig.sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "rakuten": { 3 | "app_id": "xxxxxxxxxxxxxxxxxxx" 4 | }, 5 | "locale": { 6 | "timezone": "Asia/Tokyo", 7 | "region": "asia-northeast1" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/functions/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | ../../.vscode/extensions.json -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/functions/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | ../../.vscode/settings.json -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/functions/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "typescript", 8 | "tsconfig": "tsconfig.json", 9 | "option": "watch", 10 | "problemMatcher": [ 11 | "$tsc-watch" 12 | ], 13 | "group": "build" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/functions/src/firestore-admin/feed-memo.ts: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin'; 2 | 3 | import { collectionName } from '../services/mangarel/constants'; 4 | import { FeedMemo } from '../services/mangarel/models/feed-memo'; 5 | 6 | export const saveFeedMemo = async ( 7 | db: admin.firestore.Firestore, 8 | memos: FeedMemo[], 9 | publisher: string, 10 | ) => { 11 | const memosRef = db.collection(collectionName.feedMemos); 12 | const query = await memosRef.where('publisher', '==', publisher).get(); 13 | const existingMemos = query.docs.map(doc => doc.data() as FeedMemo); 14 | let count = 0; 15 | 16 | for await (const memo of memos) { 17 | if (existingMemos.some(m => m.title === memo.title)) { 18 | continue; 19 | } else { 20 | await memosRef.doc().set({ 21 | ...memo, 22 | fetchedAt: admin.firestore.Timestamp.fromDate(new Date(0)), 23 | createdAt: admin.firestore.FieldValue.serverTimestamp(), 24 | }); 25 | count += 1; 26 | } 27 | } 28 | 29 | return count; 30 | }; 31 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/functions/src/firestore-admin/publisher.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-loop-func */ 2 | import admin from 'firebase-admin'; 3 | 4 | import { collectionName } from '../services/mangarel/constants'; 5 | import { Publisher } from '../services/mangarel/models/publisher'; 6 | 7 | export const findPublisher = async ( 8 | db: admin.firestore.Firestore, 9 | id: string, 10 | ) => { 11 | const doc = await db 12 | .collection(collectionName.publishers) 13 | .doc(id) 14 | .get(); 15 | const publisher = doc.data() as Publisher; 16 | 17 | return { ...publisher, id: doc.id }; 18 | }; 19 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/functions/src/firestore-admin/record-counter.ts: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin'; 2 | import { collectionName } from '../services/mangarel/constants'; 3 | 4 | export const addCounter = async ( 5 | db: admin.firestore.Firestore, 6 | collName: string, 7 | count = 1, 8 | ) => { 9 | const doc = db.collection(collectionName.docCounters).doc(collName); 10 | await doc.set( 11 | { 12 | count: admin.firestore.FieldValue.increment(count), 13 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 14 | }, 15 | { merge: true }, 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/functions/src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable import/no-dynamic-require */ 3 | import admin from 'firebase-admin'; 4 | import forEach from 'lodash/forEach'; 5 | import { isDevelopment } from './utils/env'; 6 | 7 | admin.initializeApp(); 8 | 9 | const functionMap = { 10 | fetchCalendar: './fetch-calendar', 11 | registerBooks: './register-books', 12 | }; 13 | 14 | const devFunctionMap = { 15 | publishers: './publishers', 16 | searchBooks: './search-books', 17 | }; 18 | 19 | const loadFunctions = (fnMap: typeof functionMap) => { 20 | forEach(fnMap, (path, functionName) => { 21 | if ( 22 | !process.env.FUNCTION_TARGET || 23 | process.env.FUNCTION_TARGET === functionName 24 | ) { 25 | module.exports[functionName] = require(path); 26 | } 27 | }); 28 | }; 29 | 30 | const fnMap = isDevelopment() 31 | ? { ...functionMap, ...devFunctionMap } 32 | : functionMap; 33 | 34 | loadFunctions(fnMap); 35 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/functions/src/publishers.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import * as functions from 'firebase-functions'; 3 | import admin from 'firebase-admin'; 4 | 5 | import { collectionName } from './services/mangarel/constants'; 6 | 7 | module.exports = functions 8 | .region('asia-northeast1') 9 | .https.onRequest(async (req, res) => { 10 | const snap = await admin 11 | .firestore() 12 | .collection(collectionName.publishers) 13 | .get(); 14 | const data = snap.docs.map(doc => doc.data()); 15 | res.send({ data }); 16 | }); 17 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/functions/src/search-books.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import * as functions from 'firebase-functions'; 3 | import admin from 'firebase-admin'; 4 | 5 | import { collectionName } from './services/mangarel/constants'; 6 | import { tokenize } from './utils/text-processor'; 7 | 8 | module.exports = functions 9 | .region(functions.config().locale.region) 10 | .https.onRequest(async (req, res) => { 11 | const searchWord = req.query.q; 12 | const booksRef = admin.firestore().collection(collectionName.books); 13 | 14 | let query = booksRef.limit(10); 15 | tokenize(searchWord).forEach(token => { 16 | query = query.where(`tokenMap.${token}`, '==', true); 17 | }); 18 | const snap = await query.get(); 19 | // const snap = await query.orderBy('publishedOn').get(); 20 | const data = snap.docs.map(doc => ({ 21 | id: doc.id, 22 | ...doc.data(), 23 | })); 24 | 25 | res.send({ data }); 26 | }); 27 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/functions/src/services/mangarel: -------------------------------------------------------------------------------- 1 | ../../../src/services/mangarel -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/functions/src/services/rakuten/models/book-item.ts: -------------------------------------------------------------------------------- 1 | export type BookItem = { 2 | title: string; 3 | titleKana: string; 4 | subTitle: string; 5 | subTitleKana: string; 6 | seriesName: string; 7 | seriesNameKana: string; 8 | contents: string; 9 | author: string; 10 | authorKana: string; 11 | publisherName: string; 12 | size: string; 13 | isbn: string; 14 | itemCaption: string; 15 | salesDate: string; 16 | itemPrice: number; 17 | listPrice: number; 18 | discountRate: number; 19 | discountPrice: number; 20 | itemUrl: string; 21 | affiliateUrl: string; 22 | smallImageUrl: string; 23 | mediumImageUrl: string; 24 | largeImageUrl: string; 25 | chirayomiUrl: string; 26 | availability: string; 27 | postageFlag: number; 28 | limitedFlag: number; 29 | reviewCount: number; 30 | reviewAverage: string; 31 | booksGenreId: string; 32 | }; 33 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/functions/src/utils: -------------------------------------------------------------------------------- 1 | ../../src/utils -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "lib": ["dom", "esnext"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "noImplicitReturns": true, 8 | "noUnusedLocals": true, 9 | "outDir": "lib", 10 | "rootDir": "src", 11 | "removeComments": true, 12 | "resolveJsonModule": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "target": "es2017", 16 | "types": ["jest", "node"], 17 | "typeRoots": ["node_modules/@types", "../node_modules/@types"] 18 | }, 19 | "compileOnSave": true, 20 | "include": ["src"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/04-firestore/mangarel-demo/public/favicon.ico -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/04-firestore/mangarel-demo/public/logo192.png -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/04-firestore/mangarel-demo/public/logo512.png -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .App-header { 12 | align-items: center; 13 | background-color: #282c34; 14 | color: white; 15 | display: flex; 16 | flex-direction: column; 17 | font-size: calc(10px + 2vmin); 18 | justify-content: center; 19 | min-height: 100vh; 20 | } 21 | 22 | .App-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes App-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | 31 | to { 32 | transform: rotate(360deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | 5 | const App: React.FC = () => { 6 | return ( 7 |
8 |
9 | logo 10 |

11 | Edit src/App.tsx and save to reload. 12 |

13 | 19 | Learn React 20 | 21 |
22 |
23 | ); 24 | }; 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 3 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 4 | sans-serif; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | margin: 0; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/src/services/mangarel/constants.ts: -------------------------------------------------------------------------------- 1 | export const collectionName = { 2 | authors: 'authors', 3 | books: 'books', 4 | users: 'users', 5 | publishers: 'publishers', 6 | docCounters: 'docCounters', 7 | feedMemos: 'feedMemos', 8 | } as const; 9 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/src/services/mangarel/models/author.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | 3 | export type Author = { 4 | id?: string; 5 | name: string; 6 | nameReading: string | null; 7 | variation: string; 8 | createdAt: firestore.Timestamp | null; 9 | updatedAt: firestore.Timestamp | null; 10 | }; 11 | 12 | export const blankAuthor: Author = { 13 | name: '', 14 | nameReading: null, 15 | variation: '', 16 | createdAt: null, 17 | updatedAt: null, 18 | }; 19 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/src/services/mangarel/models/book.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | import { Author } from './author'; 3 | import { Publisher } from './publisher'; 4 | 5 | export type Book = { 6 | id?: string; 7 | title: string; 8 | titleReading: string | null; 9 | publisherId: string; 10 | publisher: Publisher | null; 11 | authorIds: string[]; 12 | authors: Author[]; 13 | isbn: string; 14 | rbCode: string; 15 | hasImage: boolean; 16 | tokenMap: { [token: string]: boolean } | null; 17 | publishedOn: firestore.Timestamp | null; 18 | createdAt: firestore.Timestamp | null; 19 | updatedAt: firestore.Timestamp | null; 20 | }; 21 | 22 | export const blankBook: Book = { 23 | title: '', 24 | titleReading: null, 25 | publisherId: '', 26 | publisher: null, 27 | authorIds: [], 28 | authors: [], 29 | isbn: '', 30 | rbCode: '', 31 | hasImage: false, 32 | tokenMap: null, 33 | publishedOn: null, 34 | createdAt: null, 35 | updatedAt: null, 36 | }; 37 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/src/services/mangarel/models/feed-memo.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | 3 | export type FeedMemo = { 4 | id?: string; 5 | title: string | null; 6 | author: string | null; 7 | publisher: string | null; 8 | releaseDate: string | null; 9 | isbn: string | null; 10 | fetchedAt: firestore.Timestamp | null; 11 | createdAt: firestore.Timestamp | null; 12 | }; 13 | 14 | export const blankFeedMemo: FeedMemo = { 15 | title: null, 16 | author: null, 17 | publisher: null, 18 | releaseDate: null, 19 | isbn: null, 20 | fetchedAt: null, 21 | createdAt: null, 22 | }; 23 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/src/services/mangarel/models/publisher.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | 3 | export type Publisher = { 4 | id?: string; 5 | name: string; 6 | nameReading: string | null; 7 | website: string | null; 8 | createdAt: firestore.Timestamp | null; 9 | updatedAt: firestore.Timestamp | null; 10 | }; 11 | 12 | export const blankPublisher: Publisher = { 13 | name: '', 14 | nameReading: null, 15 | website: null, 16 | createdAt: null, 17 | updatedAt: null, 18 | }; 19 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/src/utils/env.ts: -------------------------------------------------------------------------------- 1 | export const isDevelopment = () => (process.env.node || '').includes('nodenv'); 2 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/src/utils/n-gram.spec.ts: -------------------------------------------------------------------------------- 1 | import { bigram, trigram } from './n-gram'; 2 | 3 | describe('Get n-grams', () => { 4 | describe('Bigram', () => { 5 | it('should be devided successfully', () => { 6 | const text = '終末のワルキューレ'; 7 | const result = bigram(text); 8 | expect(result).toEqual([ 9 | '終末', 10 | '末の', 11 | 'のワ', 12 | 'ワル', 13 | 'ルキ', 14 | 'キュ', 15 | 'ュー', 16 | 'ーレ', 17 | ]); 18 | }); 19 | }); 20 | 21 | describe('Trigram', () => { 22 | it('should be devided successfully', () => { 23 | const text = '終末のワルキューレ'; 24 | const result = trigram(text); 25 | expect(result).toEqual([ 26 | '終末の', 27 | '末のワ', 28 | 'のワル', 29 | 'ワルキ', 30 | 'ルキュ', 31 | 'キュー', 32 | 'ューレ', 33 | ]); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/src/utils/n-gram.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This function is ported from https://www.npmjs.com/package/n-gram 3 | * Copyright (c) 2014 Titus Wormer 4 | * (The MIT License) 5 | */ 6 | export const nGram = (n: number) => { 7 | if (n < 1 || n === Infinity) { 8 | throw new Error(`${n} is not a valid argument for n-gram`); 9 | } 10 | 11 | return (value: string | null) => { 12 | const nGrams: string[] = []; 13 | let index = 0; 14 | 15 | if (!value) { 16 | return nGrams; 17 | } 18 | 19 | index = value.length - n + 1; 20 | 21 | if (index < 1) { 22 | return nGrams; 23 | } 24 | 25 | // eslint-disable-next-line no-plusplus 26 | while (index--) { 27 | nGrams[index] = value.slice(index, index + n); 28 | } 29 | 30 | return nGrams; 31 | }; 32 | }; 33 | 34 | export const bigram = nGram(2); 35 | export const trigram = nGram(3); 36 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/src/utils/timer.ts: -------------------------------------------------------------------------------- 1 | export const sleep = (msec: number) => 2 | new Promise(resolve => setTimeout(() => resolve(), msec)); 3 | -------------------------------------------------------------------------------- /04-firestore/mangarel-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react", 17 | "baseUrl": "src", 18 | "incremental": true 19 | }, 20 | "include": ["src"], 21 | "exclude": ["node_modules", "build", "scripts", "functions"] 22 | } 23 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/.env.sample: -------------------------------------------------------------------------------- 1 | REACT_APP_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 2 | REACT_APP_AUTH_DOMAIN=mangarel-demo.web.app 3 | REACT_APP_DATABASE_URL=https://mangarel-demo.firebaseio.com 4 | REACT_APP_PROJECT_ID=mangarel-demo 5 | REACT_APP_APP_ID=1:xxxxxxxxxxxx:web:xxxxxxxxxxxxxxxxxxxxxx 6 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.config.js 3 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/.firebaserc.sample: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "mangarel-demo" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Dependency directories 16 | node_modules/ 17 | jspm_packages/ 18 | /.pnp 19 | .pnp.js 20 | 21 | # Testing 22 | /coverage 23 | 24 | # Production 25 | /build 26 | 27 | # TypeScript v1 declaration files 28 | typings/ 29 | 30 | # Optional npm cache directory 31 | .npm 32 | 33 | # Optional Firebase directory 34 | .firebase 35 | 36 | # Optional eslint cache 37 | .eslintcache 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | 42 | # Output of 'npm pack' 43 | *.tgz 44 | 45 | # Yarn Integrity file 46 | .yarn-integrity 47 | 48 | # IDE 49 | .idea/ 50 | .*.swp 51 | .vscode/chrome 52 | 53 | # environment variables file 54 | .env 55 | #.env.* 56 | .firebaserc 57 | 58 | # misc 59 | .DS_Store 60 | npm-debug.log* 61 | yarn-debug.log* 62 | yarn-error.log* 63 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "printWidth": 80, 4 | "semi": true, 5 | "singleQuote": true, 6 | "trailingComma": 'all', 7 | "useTabs": false, 8 | } 9 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "aaron-bond.better-comments", 4 | "CoenraadS.bracket-pair-colorizer", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "hex-ci.stylelint-plus", 8 | "jpoissonnier.vscode-styled-components", 9 | "mechatroner.rainbow-csv", 10 | "mikestead.dotenv", 11 | "oderwat.indent-rainbow", 12 | "orta.vscode-jest", 13 | "toba.vsfire", 14 | "vincaslt.highlight-matching-tag", 15 | "VisualStudioExptTeam.vscodeintellicode", 16 | "vscode-icons-team.vscode-icons" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "css.validate": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true 5 | }, 6 | "editor.tabSize": 2, 7 | "editor.wordWrap": "on", 8 | "eslint.autoFixOnSave": true, 9 | "eslint.enable": true, 10 | "eslint.packageManager": "yarn", 11 | "eslint.validate": [ 12 | { 13 | "language": "javascript", 14 | "autoFix": true 15 | }, 16 | { 17 | "language": "javascriptreact", 18 | "autoFix": true 19 | }, 20 | { 21 | "language": "typescript", 22 | "autoFix": true 23 | }, 24 | { 25 | "language": "typescriptreact", 26 | "autoFix": true 27 | } 28 | ], 29 | "stylelint.autoFixOnSave": true, 30 | "stylelint.configOverrides": { 31 | "ignoreFiles": ["node_modules/**/*.*"] 32 | }, 33 | "stylelint.enable": true, 34 | "typescript.tsdk": "node_modules/typescript/lib" 35 | } 36 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "firestore": { 3 | "rules": "firestore.rules", 4 | "indexes": "firestore.indexes.json" 5 | }, 6 | "functions": { 7 | "predeploy": "npm --prefix \"$RESOURCE_DIR\" run build" 8 | }, 9 | "hosting": { 10 | "public": "build", 11 | "ignore": [ 12 | "firebase.json", 13 | "**/.*", 14 | "**/node_modules/**" 15 | ], 16 | "rewrites": [ 17 | { 18 | "source": "**", 19 | "destination": "/index.html" 20 | } 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | "indexes": [ 3 | { 4 | "collectionGroup": "feedMemos", 5 | "queryScope": "COLLECTION", 6 | "fields": [ 7 | { 8 | "fieldPath": "isbn", 9 | "order": "ASCENDING" 10 | }, 11 | { 12 | "fieldPath": "fetchedAt", 13 | "order": "ASCENDING" 14 | } 15 | ] 16 | } 17 | ], 18 | "fieldOverrides": [] 19 | } 20 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/firestore.rules: -------------------------------------------------------------------------------- 1 | service cloud.firestore { 2 | match /databases/{database}/documents { 3 | match /{document=**} { 4 | allow read, write; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/functions/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/functions/.gitignore: -------------------------------------------------------------------------------- 1 | ## Compiled JavaScript files 2 | lib/**/*.js 3 | **/*.js.map 4 | 5 | # Typescript v1 declaration files 6 | typings/ 7 | 8 | # Testing# Testing 9 | /coverage 10 | 11 | # npm packages 12 | node_modules/ 13 | 14 | # Firebase Admin SDK credential 15 | *firebase-adminsdk*.json 16 | 17 | # Firebase environment variables file 18 | .runtimeconfig.json 19 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/functions/.node-version: -------------------------------------------------------------------------------- 1 | 10.17.0 2 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/functions/.runtimeconfig.sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "rakuten": { 3 | "app_id": "xxxxxxxxxxxxxxxxxxx" 4 | }, 5 | "locale": { 6 | "timezone": "Asia/Tokyo", 7 | "region": "asia-northeast1" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/functions/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | ../../.vscode/extensions.json -------------------------------------------------------------------------------- /05-react/mangarel-demo/functions/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | ../../.vscode/settings.json -------------------------------------------------------------------------------- /05-react/mangarel-demo/functions/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "typescript", 8 | "tsconfig": "tsconfig.json", 9 | "option": "watch", 10 | "problemMatcher": [ 11 | "$tsc-watch" 12 | ], 13 | "group": "build" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /05-react/mangarel-demo/functions/src/firestore-admin/feed-memo.ts: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin'; 2 | 3 | import { collectionName } from '../services/mangarel/constants'; 4 | import { FeedMemo } from '../services/mangarel/models/feed-memo'; 5 | 6 | export const saveFeedMemo = async ( 7 | db: admin.firestore.Firestore, 8 | memos: FeedMemo[], 9 | publisher: string, 10 | ) => { 11 | const memosRef = db.collection(collectionName.feedMemos); 12 | const query = await memosRef.where('publisher', '==', publisher).get(); 13 | const existingMemos = query.docs.map(doc => doc.data() as FeedMemo); 14 | let count = 0; 15 | 16 | for await (const memo of memos) { 17 | if (existingMemos.some(m => m.title === memo.title)) { 18 | continue; 19 | } else { 20 | await memosRef.doc().set({ 21 | ...memo, 22 | fetchedAt: admin.firestore.Timestamp.fromDate(new Date(0)), 23 | createdAt: admin.firestore.FieldValue.serverTimestamp(), 24 | }); 25 | count += 1; 26 | } 27 | } 28 | 29 | return count; 30 | }; 31 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/functions/src/firestore-admin/publisher.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-loop-func */ 2 | import admin from 'firebase-admin'; 3 | 4 | import { collectionName } from '../services/mangarel/constants'; 5 | import { Publisher } from '../services/mangarel/models/publisher'; 6 | 7 | export const findPublisher = async ( 8 | db: admin.firestore.Firestore, 9 | id: string, 10 | ) => { 11 | const doc = await db 12 | .collection(collectionName.publishers) 13 | .doc(id) 14 | .get(); 15 | const publisher = doc.data() as Publisher; 16 | 17 | return { ...publisher, id: doc.id }; 18 | }; 19 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/functions/src/firestore-admin/record-counter.ts: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin'; 2 | import { collectionName } from '../services/mangarel/constants'; 3 | 4 | export const addCounter = async ( 5 | db: admin.firestore.Firestore, 6 | collName: string, 7 | count = 1, 8 | ) => { 9 | const doc = db.collection(collectionName.docCounters).doc(collName); 10 | await doc.set( 11 | { 12 | count: admin.firestore.FieldValue.increment(count), 13 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 14 | }, 15 | { merge: true }, 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/functions/src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable import/no-dynamic-require */ 3 | import admin from 'firebase-admin'; 4 | import forEach from 'lodash/forEach'; 5 | import { isDevelopment } from './utils/env'; 6 | 7 | admin.initializeApp(); 8 | 9 | const functionMap = { 10 | fetchCalendar: './fetch-calendar', 11 | registerBooks: './register-books', 12 | }; 13 | 14 | const devFunctionMap = { 15 | publishers: './publishers', 16 | searchBooks: './search-books', 17 | }; 18 | 19 | const loadFunctions = (fnMap: typeof functionMap) => { 20 | forEach(fnMap, (path, functionName) => { 21 | if ( 22 | !process.env.FUNCTION_TARGET || 23 | process.env.FUNCTION_TARGET === functionName 24 | ) { 25 | module.exports[functionName] = require(path); 26 | } 27 | }); 28 | }; 29 | 30 | const fnMap = isDevelopment() 31 | ? { ...functionMap, ...devFunctionMap } 32 | : functionMap; 33 | 34 | loadFunctions(fnMap); 35 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/functions/src/publishers.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import * as functions from 'firebase-functions'; 3 | import admin from 'firebase-admin'; 4 | 5 | import { collectionName } from './services/mangarel/constants'; 6 | 7 | module.exports = functions 8 | .region('asia-northeast1') 9 | .https.onRequest(async (req, res) => { 10 | const snap = await admin 11 | .firestore() 12 | .collection(collectionName.publishers) 13 | .get(); 14 | const data = snap.docs.map(doc => doc.data()); 15 | res.send({ data }); 16 | }); 17 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/functions/src/search-books.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import * as functions from 'firebase-functions'; 3 | import admin from 'firebase-admin'; 4 | 5 | import { collectionName } from './services/mangarel/constants'; 6 | import { tokenize } from './utils/text-processor'; 7 | 8 | module.exports = functions 9 | .region(functions.config().locale.region) 10 | .https.onRequest(async (req, res) => { 11 | const searchWord = req.query.q; 12 | const booksRef = admin.firestore().collection(collectionName.books); 13 | 14 | let query = booksRef.limit(10); 15 | tokenize(searchWord).forEach(token => { 16 | query = query.where(`tokenMap.${token}`, '==', true); 17 | }); 18 | const snap = await query.get(); 19 | // const snap = await query.orderBy('publishedOn').get(); 20 | const data = snap.docs.map(doc => ({ 21 | id: doc.id, 22 | ...doc.data(), 23 | })); 24 | 25 | res.send({ data }); 26 | }); 27 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/functions/src/services/mangarel: -------------------------------------------------------------------------------- 1 | ../../../src/services/mangarel -------------------------------------------------------------------------------- /05-react/mangarel-demo/functions/src/services/rakuten/models/book-item.ts: -------------------------------------------------------------------------------- 1 | export type BookItem = { 2 | title: string; 3 | titleKana: string; 4 | subTitle: string; 5 | subTitleKana: string; 6 | seriesName: string; 7 | seriesNameKana: string; 8 | contents: string; 9 | author: string; 10 | authorKana: string; 11 | publisherName: string; 12 | size: string; 13 | isbn: string; 14 | itemCaption: string; 15 | salesDate: string; 16 | itemPrice: number; 17 | listPrice: number; 18 | discountRate: number; 19 | discountPrice: number; 20 | itemUrl: string; 21 | affiliateUrl: string; 22 | smallImageUrl: string; 23 | mediumImageUrl: string; 24 | largeImageUrl: string; 25 | chirayomiUrl: string; 26 | availability: string; 27 | postageFlag: number; 28 | limitedFlag: number; 29 | reviewCount: number; 30 | reviewAverage: string; 31 | booksGenreId: string; 32 | }; 33 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/functions/src/utils: -------------------------------------------------------------------------------- 1 | ../../src/utils -------------------------------------------------------------------------------- /05-react/mangarel-demo/functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "lib": ["dom", "esnext"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "noImplicitReturns": true, 8 | "noUnusedLocals": true, 9 | "outDir": "lib", 10 | "rootDir": "src", 11 | "removeComments": true, 12 | "resolveJsonModule": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "target": "es2017", 16 | "types": ["jest", "node"], 17 | "typeRoots": ["node_modules/@types", "../node_modules/@types"] 18 | }, 19 | "compileOnSave": true, 20 | "include": ["src"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/05-react/mangarel-demo/public/favicon.ico -------------------------------------------------------------------------------- /05-react/mangarel-demo/public/images/comingsoon-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/05-react/mangarel-demo/public/images/comingsoon-large.png -------------------------------------------------------------------------------- /05-react/mangarel-demo/public/images/comingsoon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/05-react/mangarel-demo/public/images/comingsoon-small.png -------------------------------------------------------------------------------- /05-react/mangarel-demo/public/mangarel-logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/05-react/mangarel-demo/public/mangarel-logo192.png -------------------------------------------------------------------------------- /05-react/mangarel-demo/public/mangarel-logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/05-react/mangarel-demo/public/mangarel-logo512.png -------------------------------------------------------------------------------- /05-react/mangarel-demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Mangarel", 3 | "name": "Mangarel 漫画発売情報アプリ", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "mangarel-logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "mangarel-logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#68e1e3", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .App-header { 12 | align-items: center; 13 | background-color: #282c34; 14 | color: white; 15 | display: flex; 16 | flex-direction: column; 17 | font-size: calc(10px + 2vmin); 18 | justify-content: center; 19 | min-height: 100vh; 20 | } 21 | 22 | .App-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes App-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | 31 | to { 32 | transform: rotate(360deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { Redirect, Route, Switch } from 'react-router'; 3 | 4 | import Book from 'components/Book'; 5 | import Home from 'components/Home'; 6 | import NavigationBar from 'components/common/menubar/NavigationBar'; 7 | import Search from 'components/Search'; 8 | import Spacer from 'components/common/atoms/Spacer'; 9 | import paths from './paths'; 10 | import './App.css'; 11 | 12 | const App: FC = () => ( 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | ); 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/FirebaseApp.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import firebase from 'firebase/app'; 3 | import 'firebase/auth'; 4 | import 'firebase/firestore'; 5 | 6 | import { FirebaseContext } from './contexts'; 7 | 8 | const FirebaseApp: FC = ({ children }) => { 9 | const db = firebase.firestore(); 10 | 11 | return ( 12 | 13 | {children} 14 | 15 | ); 16 | }; 17 | 18 | export default FirebaseApp; 19 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/Book/RakutenBooksButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useContext } from 'react'; 2 | import { isFuture } from 'date-fns'; 3 | 4 | import { ThemeContext } from 'contexts'; 5 | import { Book } from 'services/mangarel/models/book'; 6 | import LinkButton from 'components/common/atoms/LinkButton'; 7 | 8 | const RakutenBooksButton: FC<{ book: Book }> = ({ book }) => { 9 | const theme = useContext(ThemeContext); 10 | const linkText = 11 | book.publishedOn && isFuture(book.publishedOn.toDate()) 12 | ? '楽天ブックスで予約注文する' 13 | : '楽天ブックスの商品ページに行く'; 14 | 15 | return ( 16 | 20 | } 21 | link={`https://books.rakuten.co.jp/rb/${book.rbCode}/`} 22 | > 23 | {linkText} 24 | 25 | ); 26 | }; 27 | 28 | export default RakutenBooksButton; 29 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/Book/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import BookMain from 'containers/Book/BookMain'; 4 | 5 | const Home: React.FC = () => ( 6 |
7 | 8 |
9 | ); 10 | 11 | export default Home; 12 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/Home/Calendar.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import { Book } from 'services/mangarel/models/book'; 4 | import DividingHeader from 'components/common/header/DividingHeader'; 5 | import CalendarList from 'components/common/list/CalendarList'; 6 | import ListLoader from 'components/common/atoms/ListLoader'; 7 | 8 | type CalendarProps = { books: Book[]; loading?: boolean }; 9 | 10 | const Calendar: FC = ({ books, loading }) => ( 11 |
12 | 13 | もうすぐ発売の新刊 14 | 15 | {loading ? : } 16 |
17 | ); 18 | 19 | export default Calendar; 20 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Calendar from 'containers/Home/Calendar'; 4 | 5 | const Home: React.FC = () => ( 6 |
7 |
8 | 9 |
10 |
11 | ); 12 | 13 | export default Home; 14 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/Search/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import SearchUnit from 'containers/Search/SearchUnit'; 4 | 5 | const Search: React.FC = () => ( 6 |
7 | 8 |
9 | ); 10 | 11 | export default Search; 12 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/atoms/CardAttribute.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useContext } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Card from 'semantic-ui-react/dist/commonjs/views/Card'; 4 | import { ThemeContext } from 'contexts'; 5 | 6 | const CardAttribute: FC = ({ children, ...props }) => { 7 | const theme = useContext(ThemeContext); 8 | const Attribute = styled(Card.Meta)` 9 | &&& { 10 | color: ${theme.color.lightGray}; 11 | font-size: 0.66rem; 12 | text-align: right; 13 | } 14 | `; 15 | 16 | return {children}; 17 | }; 18 | 19 | export default CardAttribute; 20 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/atoms/CardContent.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useContext } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Card from 'semantic-ui-react/dist/commonjs/views/Card'; 4 | import { CardContentProps } from 'semantic-ui-react/dist/commonjs/views/Card/CardContent'; 5 | import { ThemeContext } from 'contexts'; 6 | 7 | const CardContent: FC = ({ children, ...props }) => { 8 | const theme = useContext(ThemeContext); 9 | const Content = styled(Card.Content)` 10 | &&& { 11 | color: ${theme.color.black}; 12 | padding: 0.55rem 0.75rem 0.4rem; 13 | } 14 | `; 15 | 16 | return {children}; 17 | }; 18 | 19 | export default CardContent; 20 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/atoms/CardDescription.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useContext } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Card from 'semantic-ui-react/dist/commonjs/views/Card'; 4 | import { CardDescriptionProps } from 'semantic-ui-react/dist/commonjs/views/Card/CardDescription'; 5 | import { ThemeContext } from 'contexts'; 6 | 7 | const CardDescription: FC = ({ children, ...props }) => { 8 | const theme = useContext(ThemeContext); 9 | const Desciption = styled(Card.Description)` 10 | &&& { 11 | color: ${theme.color.black}; 12 | font-size: 0.8rem; 13 | } 14 | 15 | .separator { 16 | margin: 0 0.05rem; 17 | } 18 | 19 | strong { 20 | margin: 0 0.15rem !important; 21 | } 22 | `; 23 | 24 | return {children}; 25 | }; 26 | 27 | export default CardDescription; 28 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/atoms/CardInfo.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useContext } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { ThemeContext } from 'contexts'; 4 | 5 | const CardInfo: FC = ({ children, ...props }) => { 6 | const theme = useContext(ThemeContext); 7 | const Info = styled.div` 8 | && { 9 | color: ${theme.color.black}; 10 | height: 3rem; 11 | line-height: 1.15rem; 12 | margin-top: 0.2rem; 13 | } 14 | `; 15 | 16 | return {children}; 17 | }; 18 | 19 | export default CardInfo; 20 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/atoms/CardSummary.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useContext } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Card from 'semantic-ui-react/dist/commonjs/views/Card'; 4 | import { CardDescriptionProps } from 'semantic-ui-react/dist/commonjs/views/Card/CardDescription'; 5 | import { ThemeContext } from 'contexts'; 6 | 7 | const CardDescription: FC = ({ children, ...props }) => { 8 | const theme = useContext(ThemeContext); 9 | const Desciption = styled(Card.Description)` 10 | &&& { 11 | color: ${theme.color.gray}; 12 | font-size: 0.55rem; 13 | line-height: 1rem; 14 | margin-top: 0.75rem; 15 | } 16 | `; 17 | 18 | return {children}; 19 | }; 20 | 21 | export default CardDescription; 22 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/atoms/CardTitle.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useContext } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Card from 'semantic-ui-react/dist/commonjs/views/Card'; 4 | import { CardHeaderProps } from 'semantic-ui-react/dist/commonjs/views/Card/CardHeader'; 5 | import { ThemeContext } from 'contexts'; 6 | 7 | const CardTitle: FC = ({ children, ...props }) => { 8 | const theme = useContext(ThemeContext); 9 | const Title = styled(Card.Header)` 10 | color: ${theme.color.black} !important; 11 | font-size: 0.9rem !important; 12 | font-weight: 600 !important; 13 | margin-top: 0.1rem !important; 14 | `; 15 | 16 | return {children}; 17 | }; 18 | 19 | export default CardTitle; 20 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/atoms/ItemCard.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import Card from 'semantic-ui-react/dist/commonjs/views/Card'; 3 | 4 | const ItemCard = styled(Card)` 5 | &&& { 6 | margin: 0.75rem 0 0; 7 | padding: 0; 8 | width: auto; 9 | } 10 | `; 11 | 12 | export default ItemCard; 13 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/atoms/LargeCoverImage.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import styled from '@emotion/styled'; 4 | import Image, { 5 | ImageProps, 6 | } from 'semantic-ui-react/dist/commonjs/elements/Image'; 7 | 8 | const ImageWrapper = styled.div` 9 | img { 10 | border: 1px #efefef solid; 11 | box-shadow: 0 1px 6px 1px rgba(0, 0, 0, 0.15); 12 | margin-bottom: 0 !important; 13 | } 14 | 15 | @media screen and (max-width: 1024px) { 16 | img { 17 | width: 300px !important; 18 | } 19 | } 20 | 21 | @media screen and (max-width: 480px) { 22 | img { 23 | width: 120px !important; 24 | } 25 | } 26 | `; 27 | 28 | const LargeCoverImage: FC = ({ children, ...props }) => ( 29 | 30 | 31 | {children} 32 | 33 | 34 | ); 35 | 36 | export default LargeCoverImage; 37 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/atoms/ListLoader.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Loader from 'semantic-ui-react/dist/commonjs/elements/Loader'; 4 | import Segment from 'semantic-ui-react/dist/commonjs/elements/Segment'; 5 | 6 | const LoaderWrapper = styled(Segment)` 7 | &&& { 8 | margin-top: 4rem; 9 | } 10 | `; 11 | 12 | const ListLoader: FC = () => ( 13 | 14 | 15 | 16 | ); 17 | 18 | export default ListLoader; 19 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/atoms/MainFrame.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import styled from '@emotion/styled'; 4 | import Container, { 5 | ContainerProps, 6 | } from 'semantic-ui-react/dist/commonjs/elements/Container'; 7 | 8 | const ContainerFrame = styled(Container)` 9 | && { 10 | margin-top: 1em; 11 | } 12 | `; 13 | 14 | const MainFrame: FC = ({ children, ...props }) => ( 15 | {children} 16 | ); 17 | 18 | export default MainFrame; 19 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/atoms/SmallCoverImage.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import styled from '@emotion/styled'; 4 | import Image, { 5 | ImageProps, 6 | } from 'semantic-ui-react/dist/commonjs/elements/Image'; 7 | 8 | const CoverImage = styled(Image)` 9 | filter: drop-shadow(2px 2px 2px rgba(0, 0, 0, 0.1)); 10 | margin-bottom: 0 !important; 11 | width: 55px !important; 12 | `; 13 | 14 | const SmallCoverImage: FC = ({ children, ...props }) => ( 15 | 16 | {children} 17 | 18 | ); 19 | 20 | export default SmallCoverImage; 21 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/atoms/Spacer.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | const Spacer: FC<{ height?: number }> = ({ height = 3 }) => { 5 | const SpacerSegment = styled.div` 6 | clear: both; 7 | height: ${height}rem; 8 | width: auto; 9 | `; 10 | 11 | return ; 12 | }; 13 | 14 | export default Spacer; 15 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/list/BookList.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | import { Book } from 'services/mangarel/models/book'; 5 | import BookCard from 'components/common/card/BookCard'; 6 | import ListLoader from 'components/common/atoms/ListLoader'; 7 | 8 | const ListWrapper = styled.div` 9 | margin: 1rem 0.5rem; 10 | `; 11 | 12 | const BookList: FC<{ books: Book[]; loading?: boolean }> = ({ 13 | books, 14 | loading = false, 15 | }) => 16 | loading ? ( 17 | 18 | ) : ( 19 | 20 | {books.map(book => ( 21 | 22 | ))} 23 | 24 | ); 25 | 26 | export default BookList; 27 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/list/CalendarList.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | import { Book } from 'services/mangarel/models/book'; 5 | import BookCard from 'components/common/card/BookCard'; 6 | 7 | const CalendarList: FC<{ books: Book[] }> = ({ books }) => { 8 | const ListWrapper = styled.div` 9 | margin: 1rem 0.5rem; 10 | `; 11 | 12 | return ( 13 | 14 | {books.map(book => ( 15 | 16 | ))} 17 | 18 | ); 19 | }; 20 | 21 | export default CalendarList; 22 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/work/AttributesField.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Grid from 'semantic-ui-react/dist/commonjs/collections/Grid'; 4 | 5 | const GridWrapper = styled.div` 6 | margin: 0.5rem 1rem 0.75rem; 7 | padding-top: 1rem; 8 | width: auto; 9 | `; 10 | 11 | const ButtonGroup: FC = ({ children }) => ( 12 | 13 | 14 | {children} 15 | 16 | 17 | ); 18 | 19 | export default ButtonGroup; 20 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/work/ButtonGroup.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | const Group = styled.div` 5 | padding-top: 1rem; 6 | width: auto; 7 | `; 8 | 9 | const ButtonGroup: FC = ({ children }) => {children}; 10 | 11 | export default ButtonGroup; 12 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/work/CardGroup.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | const Group = styled.div` 5 | padding: 0 0.11rem 0.2rem; 6 | width: auto; 7 | `; 8 | 9 | const CardGroup: FC = ({ children }) => {children}; 10 | 11 | export default CardGroup; 12 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/work/WorkAuthors.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | import React, { FC, useContext } from 'react'; 3 | import styled from '@emotion/styled'; 4 | 5 | import { ThemeContext } from 'contexts'; 6 | import { Author } from 'services/mangarel/models/author'; 7 | 8 | const WorkAuthors: FC<{ authors: Author[] | null }> = ({ authors }) => { 9 | const theme = useContext(ThemeContext); 10 | const AuthorInfo = styled.div` 11 | &&& { 12 | color: ${theme.color.gray}; 13 | font-size: 0.85rem; 14 | font-weight: 600; 15 | margin: 0 0 1rem !important; 16 | padding-top: 0 !important; 17 | } 18 | `; 19 | 20 | return ( 21 | 22 | {authors && 23 | authors.map(author => ( 24 | 25 | {author.name} 26 | 27 | ))} 28 | 29 | ); 30 | }; 31 | 32 | export default WorkAuthors; 33 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/work/WorkPlaceHolder.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Placeholder from 'semantic-ui-react/dist/commonjs/elements/Placeholder'; 4 | 5 | const MainPlaceholder = styled(Placeholder)` 6 | &&& { 7 | margin: 1rem 0.5rem; 8 | } 9 | 10 | .image.header::after { 11 | height: 11.5rem !important; 12 | margin-left: 8.5rem !important; 13 | } 14 | `; 15 | 16 | const WorkPlaceholder: FC = () => ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | 29 | export default WorkPlaceholder; 30 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/components/common/work/WorkTitle.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useContext } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Header from 'semantic-ui-react/dist/commonjs/elements/Header'; 4 | 5 | import { ThemeContext } from 'contexts'; 6 | 7 | type WorkTitleProps = { 8 | title: string; 9 | }; 10 | 11 | const WorkTitle: FC = ({ title }) => { 12 | const theme = useContext(ThemeContext); 13 | const Title = styled(Header)` 14 | &&& { 15 | color: ${theme.color.black}; 16 | font-size: 1rem; 17 | font-weight: 600; 18 | margin: 0.75rem 0; 19 | padding-top: 0.25rem; 20 | } 21 | `; 22 | 23 | return {title}; 24 | }; 25 | 26 | export default WorkTitle; 27 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/containers/Book/BookMain.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { useHistory, useParams } from 'react-router'; 3 | 4 | import useBook from 'hooks/use-book'; 5 | import BookMain from 'components/Book/BookMain'; 6 | import paths from 'paths'; 7 | 8 | const BookMainContainer: FC = () => { 9 | const history = useHistory(); 10 | const { bookId } = useParams(); 11 | if (!bookId) history.replace(paths.home); 12 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 13 | const { book } = useBook(bookId!); 14 | 15 | return book ? :
; 16 | }; 17 | 18 | export default BookMainContainer; 19 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/containers/Home/Calendar.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import Calendar from 'components/Home/Calendar'; 4 | import useBooks from 'hooks/use-books'; 5 | 6 | const CalendarContainer: FC = () => { 7 | const { books, loading } = useBooks({ limit: 50 }); 8 | 9 | return ; 10 | }; 11 | 12 | export default CalendarContainer; 13 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/containers/Search/SearchUnit.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, SyntheticEvent, useState } from 'react'; 2 | 3 | import useBookSearch from 'hooks/use-book-search'; 4 | import SearchForm from 'components/Search/SearchForm'; 5 | import BookList from 'components/common/list/BookList'; 6 | 7 | const SearchUnit: FC = () => { 8 | const [values, setValues] = useState({ q: '' }); 9 | const { books, loading } = useBookSearch(values.q, { limit: 20 }); 10 | 11 | const handleChange = ( 12 | targetName: string, 13 | newValue: string, 14 | event?: SyntheticEvent, 15 | ) => { 16 | if (event) event.persist(); 17 | 18 | setValues(v => ({ ...v, [targetName]: newValue })); 19 | }; 20 | 21 | return ( 22 |
23 | 24 | 25 |
26 | ); 27 | }; 28 | 29 | export default SearchUnit; 30 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/contexts.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { createContext } from 'react'; 3 | import mangarelTheme from './theme'; 4 | 5 | type FirebaseContextValue = { 6 | db: firebase.firestore.Firestore | null; 7 | }; 8 | 9 | export const FirebaseContext = createContext({ 10 | db: null, 11 | }); 12 | 13 | export const ThemeContext = createContext( 14 | (null as unknown) as typeof mangarelTheme, 15 | ); 16 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/firebase-config.ts: -------------------------------------------------------------------------------- 1 | const firebaseConfig = { 2 | apiKey: process.env.REACT_APP_API_KEY, 3 | authDomain: process.env.REACT_APP_AUTH_DOMAIN, 4 | databaseURL: process.env.REACT_APP_DATABASE_URL, 5 | projectId: process.env.REACT_APP_PROJECT_ID, 6 | storageBucket: process.env.REACT_APP_STORAGE_BUCKET, 7 | messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID, 8 | }; 9 | 10 | export default firebaseConfig; 11 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 3 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 4 | sans-serif; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | margin: 0; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/paths.ts: -------------------------------------------------------------------------------- 1 | const paths = { 2 | book: '/book/:bookId([0-9]{13})', 3 | home: '/', 4 | search: '/search', 5 | signin: '/signin', 6 | }; 7 | 8 | export default paths; 9 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/services/mangarel/constants.ts: -------------------------------------------------------------------------------- 1 | export const collectionName = { 2 | authors: 'authors', 3 | books: 'books', 4 | users: 'users', 5 | publishers: 'publishers', 6 | docCounters: 'docCounters', 7 | feedMemos: 'feedMemos', 8 | } as const; 9 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/services/mangarel/models/author.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | 3 | export type Author = { 4 | id?: string; 5 | name: string; 6 | nameReading: string | null; 7 | variation: string; 8 | createdAt: firestore.Timestamp | null; 9 | updatedAt: firestore.Timestamp | null; 10 | }; 11 | 12 | export const blankAuthor: Author = { 13 | name: '', 14 | nameReading: null, 15 | variation: '', 16 | createdAt: null, 17 | updatedAt: null, 18 | }; 19 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/services/mangarel/models/book.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | import { Author } from './author'; 3 | import { Publisher } from './publisher'; 4 | 5 | export type Book = { 6 | id?: string; 7 | title: string; 8 | titleReading: string | null; 9 | publisherId: string; 10 | publisher: Publisher | null; 11 | authorIds: string[]; 12 | authors: Author[]; 13 | isbn: string; 14 | rbCode: string; 15 | hasImage: boolean; 16 | tokenMap: { [token: string]: boolean } | null; 17 | publishedOn: firestore.Timestamp | null; 18 | createdAt: firestore.Timestamp | null; 19 | updatedAt: firestore.Timestamp | null; 20 | }; 21 | 22 | export const blankBook: Book = { 23 | title: '', 24 | titleReading: null, 25 | publisherId: '', 26 | publisher: null, 27 | authorIds: [], 28 | authors: [], 29 | isbn: '', 30 | rbCode: '', 31 | hasImage: false, 32 | tokenMap: null, 33 | publishedOn: null, 34 | createdAt: null, 35 | updatedAt: null, 36 | }; 37 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/services/mangarel/models/feed-memo.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | 3 | export type FeedMemo = { 4 | id?: string; 5 | title: string | null; 6 | author: string | null; 7 | publisher: string | null; 8 | releaseDate: string | null; 9 | isbn: string | null; 10 | fetchedAt: firestore.Timestamp | null; 11 | createdAt: firestore.Timestamp | null; 12 | }; 13 | 14 | export const blankFeedMemo: FeedMemo = { 15 | title: null, 16 | author: null, 17 | publisher: null, 18 | releaseDate: null, 19 | isbn: null, 20 | fetchedAt: null, 21 | createdAt: null, 22 | }; 23 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/services/mangarel/models/publisher.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | 3 | export type Publisher = { 4 | id?: string; 5 | name: string; 6 | nameReading: string | null; 7 | website: string | null; 8 | createdAt: firestore.Timestamp | null; 9 | updatedAt: firestore.Timestamp | null; 10 | }; 11 | 12 | export const blankPublisher: Publisher = { 13 | name: '', 14 | nameReading: null, 15 | website: null, 16 | createdAt: null, 17 | updatedAt: null, 18 | }; 19 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/theme.ts: -------------------------------------------------------------------------------- 1 | const mangarelTheme = { 2 | color: { 3 | primary: '#68e1e3', 4 | secondary: '#ff6394', 5 | tertiary: '#84ca66', 6 | link: '#18c2c8', 7 | white: '#ffffff', 8 | black: '#1e1e2a', 9 | gray: '#808080', 10 | lightGray: '#979797', 11 | indigo: '#5cb2cc', 12 | ultraLightGray: '#e4e4e4', 13 | amazon: '#f08705', 14 | google: '#1a73e8', 15 | rakuten: '#d86060', 16 | }, 17 | }; 18 | 19 | export default mangarelTheme; 20 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/utils/env.ts: -------------------------------------------------------------------------------- 1 | export const isDevelopment = () => (process.env.node || '').includes('nodenv'); 2 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/utils/n-gram.spec.ts: -------------------------------------------------------------------------------- 1 | import { bigram, trigram } from './n-gram'; 2 | 3 | describe('Get n-grams', () => { 4 | describe('Bigram', () => { 5 | it('should be devided successfully', () => { 6 | const text = '終末のワルキューレ'; 7 | const result = bigram(text); 8 | expect(result).toEqual([ 9 | '終末', 10 | '末の', 11 | 'のワ', 12 | 'ワル', 13 | 'ルキ', 14 | 'キュ', 15 | 'ュー', 16 | 'ーレ', 17 | ]); 18 | }); 19 | }); 20 | 21 | describe('Trigram', () => { 22 | it('should be devided successfully', () => { 23 | const text = '終末のワルキューレ'; 24 | const result = trigram(text); 25 | expect(result).toEqual([ 26 | '終末の', 27 | '末のワ', 28 | 'のワル', 29 | 'ワルキ', 30 | 'ルキュ', 31 | 'キュー', 32 | 'ューレ', 33 | ]); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/utils/n-gram.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This function is ported from https://www.npmjs.com/package/n-gram 3 | * Copyright (c) 2014 Titus Wormer 4 | * (The MIT License) 5 | */ 6 | export const nGram = (n: number) => { 7 | if (n < 1 || n === Infinity) { 8 | throw new Error(`${n} is not a valid argument for n-gram`); 9 | } 10 | 11 | return (value: string | null) => { 12 | const nGrams: string[] = []; 13 | let index = 0; 14 | 15 | if (!value) { 16 | return nGrams; 17 | } 18 | 19 | index = value.length - n + 1; 20 | 21 | if (index < 1) { 22 | return nGrams; 23 | } 24 | 25 | // eslint-disable-next-line no-plusplus 26 | while (index--) { 27 | nGrams[index] = value.slice(index, index + n); 28 | } 29 | 30 | return nGrams; 31 | }; 32 | }; 33 | 34 | export const bigram = nGram(2); 35 | export const trigram = nGram(3); 36 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/src/utils/timer.ts: -------------------------------------------------------------------------------- 1 | export const sleep = (msec: number) => 2 | new Promise(resolve => setTimeout(() => resolve(), msec)); 3 | -------------------------------------------------------------------------------- /05-react/mangarel-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react", 17 | "baseUrl": "src", 18 | "incremental": true 19 | }, 20 | "include": ["src"], 21 | "exclude": ["node_modules", "build", "scripts", "functions"] 22 | } 23 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/.env.sample: -------------------------------------------------------------------------------- 1 | REACT_APP_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 2 | REACT_APP_AUTH_DOMAIN=mangarel-demo.web.app 3 | REACT_APP_DATABASE_URL=https://mangarel-demo.firebaseio.com 4 | REACT_APP_PROJECT_ID=mangarel-demo 5 | REACT_APP_APP_ID=1:xxxxxxxxxxxx:web:xxxxxxxxxxxxxxxxxxxxxx 6 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.config.js 3 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/.firebaserc.sample: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "mangarel-demo" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Dependency directories 16 | node_modules/ 17 | jspm_packages/ 18 | /.pnp 19 | .pnp.js 20 | 21 | # Testing 22 | /coverage 23 | 24 | # Production 25 | /build 26 | 27 | # TypeScript v1 declaration files 28 | typings/ 29 | 30 | # Optional npm cache directory 31 | .npm 32 | 33 | # Optional Firebase directory 34 | .firebase 35 | 36 | # Optional eslint cache 37 | .eslintcache 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | 42 | # Output of 'npm pack' 43 | *.tgz 44 | 45 | # Yarn Integrity file 46 | .yarn-integrity 47 | 48 | # IDE 49 | .idea/ 50 | .*.swp 51 | .vscode/chrome 52 | 53 | # environment variables file 54 | .env 55 | #.env.* 56 | .firebaserc 57 | 58 | # misc 59 | .DS_Store 60 | npm-debug.log* 61 | yarn-debug.log* 62 | yarn-error.log* 63 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "printWidth": 80, 4 | "semi": true, 5 | "singleQuote": true, 6 | "trailingComma": 'all', 7 | "useTabs": false, 8 | } 9 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "aaron-bond.better-comments", 4 | "CoenraadS.bracket-pair-colorizer", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "hex-ci.stylelint-plus", 8 | "jpoissonnier.vscode-styled-components", 9 | "mechatroner.rainbow-csv", 10 | "mikestead.dotenv", 11 | "oderwat.indent-rainbow", 12 | "orta.vscode-jest", 13 | "toba.vsfire", 14 | "vincaslt.highlight-matching-tag", 15 | "VisualStudioExptTeam.vscodeintellicode", 16 | "vscode-icons-team.vscode-icons" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "css.validate": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true 5 | }, 6 | "editor.tabSize": 2, 7 | "editor.wordWrap": "on", 8 | "eslint.autoFixOnSave": true, 9 | "eslint.enable": true, 10 | "eslint.packageManager": "yarn", 11 | "eslint.validate": [ 12 | { 13 | "language": "javascript", 14 | "autoFix": true 15 | }, 16 | { 17 | "language": "javascriptreact", 18 | "autoFix": true 19 | }, 20 | { 21 | "language": "typescript", 22 | "autoFix": true 23 | }, 24 | { 25 | "language": "typescriptreact", 26 | "autoFix": true 27 | } 28 | ], 29 | "stylelint.autoFixOnSave": true, 30 | "stylelint.configOverrides": { 31 | "ignoreFiles": ["node_modules/**/*.*"] 32 | }, 33 | "stylelint.enable": true, 34 | "typescript.tsdk": "node_modules/typescript/lib" 35 | } 36 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "firestore": { 3 | "rules": "firestore.rules", 4 | "indexes": "firestore.indexes.json" 5 | }, 6 | "functions": { 7 | "predeploy": "npm --prefix \"$RESOURCE_DIR\" run build" 8 | }, 9 | "hosting": { 10 | "public": "build", 11 | "ignore": [ 12 | "firebase.json", 13 | "**/.*", 14 | "**/node_modules/**" 15 | ], 16 | "rewrites": [ 17 | { 18 | "source": "**", 19 | "destination": "/index.html" 20 | } 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | "indexes": [ 3 | { 4 | "collectionGroup": "feedMemos", 5 | "queryScope": "COLLECTION", 6 | "fields": [ 7 | { 8 | "fieldPath": "isbn", 9 | "order": "ASCENDING" 10 | }, 11 | { 12 | "fieldPath": "fetchedAt", 13 | "order": "ASCENDING" 14 | } 15 | ] 16 | } 17 | ], 18 | "fieldOverrides": [] 19 | } 20 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/functions/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/functions/.gitignore: -------------------------------------------------------------------------------- 1 | ## Compiled JavaScript files 2 | lib/**/*.js 3 | **/*.js.map 4 | 5 | # Typescript v1 declaration files 6 | typings/ 7 | 8 | # Testing# Testing 9 | /coverage 10 | 11 | # npm packages 12 | node_modules/ 13 | 14 | # Firebase Admin SDK credential 15 | *firebase-adminsdk*.json 16 | 17 | # Firebase environment variables file 18 | .runtimeconfig.json 19 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/functions/.node-version: -------------------------------------------------------------------------------- 1 | 10.17.0 2 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/functions/.runtimeconfig.sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "rakuten": { 3 | "app_id": "xxxxxxxxxxxxxxxxxxx" 4 | }, 5 | "locale": { 6 | "timezone": "Asia/Tokyo", 7 | "region": "asia-northeast1" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/functions/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "aaron-bond.better-comments", 4 | "CoenraadS.bracket-pair-colorizer", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "hex-ci.stylelint-plus", 8 | "jpoissonnier.vscode-styled-components", 9 | "mechatroner.rainbow-csv", 10 | "mikestead.dotenv", 11 | "oderwat.indent-rainbow", 12 | "orta.vscode-jest", 13 | "toba.vsfire", 14 | "vincaslt.highlight-matching-tag", 15 | "VisualStudioExptTeam.vscodeintellicode", 16 | "vscode-icons-team.vscode-icons" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/functions/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "css.validate": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true 5 | }, 6 | "editor.tabSize": 2, 7 | "editor.wordWrap": "on", 8 | "eslint.autoFixOnSave": true, 9 | "eslint.enable": true, 10 | "eslint.packageManager": "yarn", 11 | "eslint.validate": [ 12 | { 13 | "language": "javascript", 14 | "autoFix": true 15 | }, 16 | { 17 | "language": "javascriptreact", 18 | "autoFix": true 19 | }, 20 | { 21 | "language": "typescript", 22 | "autoFix": true 23 | }, 24 | { 25 | "language": "typescriptreact", 26 | "autoFix": true 27 | } 28 | ], 29 | "stylelint.autoFixOnSave": true, 30 | "stylelint.configOverrides": { 31 | "ignoreFiles": ["node_modules/**/*.*"] 32 | }, 33 | "stylelint.enable": true, 34 | "typescript.tsdk": "node_modules/typescript/lib" 35 | } 36 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/functions/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "typescript", 8 | "tsconfig": "tsconfig.json", 9 | "option": "watch", 10 | "problemMatcher": [ 11 | "$tsc-watch" 12 | ], 13 | "group": "build" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /06-auth/mangarel-demo/functions/src/firestore-admin/feed-memo.ts: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin'; 2 | 3 | import { collectionName } from '../services/mangarel/constants'; 4 | import { FeedMemo } from '../services/mangarel/models/feed-memo'; 5 | 6 | export const saveFeedMemo = async ( 7 | db: admin.firestore.Firestore, 8 | memos: FeedMemo[], 9 | publisher: string, 10 | ) => { 11 | const memosRef = db.collection(collectionName.feedMemos); 12 | const query = await memosRef.where('publisher', '==', publisher).get(); 13 | const existingMemos = query.docs.map(doc => doc.data() as FeedMemo); 14 | let count = 0; 15 | 16 | for await (const memo of memos) { 17 | if (existingMemos.some(m => m.title === memo.title)) { 18 | continue; 19 | } else { 20 | await memosRef.doc().set({ 21 | ...memo, 22 | fetchedAt: admin.firestore.Timestamp.fromDate(new Date(0)), 23 | createdAt: admin.firestore.FieldValue.serverTimestamp(), 24 | }); 25 | count += 1; 26 | } 27 | } 28 | 29 | return count; 30 | }; 31 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/functions/src/firestore-admin/publisher.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-loop-func */ 2 | import admin from 'firebase-admin'; 3 | 4 | import { collectionName } from '../services/mangarel/constants'; 5 | import { Publisher } from '../services/mangarel/models/publisher'; 6 | 7 | export const findPublisher = async ( 8 | db: admin.firestore.Firestore, 9 | id: string, 10 | ) => { 11 | const doc = await db 12 | .collection(collectionName.publishers) 13 | .doc(id) 14 | .get(); 15 | const publisher = doc.data() as Publisher; 16 | 17 | return { ...publisher, id: doc.id }; 18 | }; 19 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/functions/src/firestore-admin/record-counter.ts: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin'; 2 | import { collectionName } from '../services/mangarel/constants'; 3 | 4 | export const addCounter = async ( 5 | db: admin.firestore.Firestore, 6 | collName: string, 7 | count = 1, 8 | ) => { 9 | const doc = db.collection(collectionName.docCounters).doc(collName); 10 | await doc.set( 11 | { 12 | count: admin.firestore.FieldValue.increment(count), 13 | updatedAt: admin.firestore.FieldValue.serverTimestamp(), 14 | }, 15 | { merge: true }, 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/functions/src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable import/no-dynamic-require */ 3 | import admin from 'firebase-admin'; 4 | import forEach from 'lodash/forEach'; 5 | import { isDevelopment } from './utils/env'; 6 | 7 | admin.initializeApp(); 8 | 9 | const functionMap = { 10 | fetchCalendar: './fetch-calendar', 11 | registerBooks: './register-books', 12 | }; 13 | 14 | const devFunctionMap = { 15 | publishers: './publishers', 16 | searchBooks: './search-books', 17 | }; 18 | 19 | const loadFunctions = (fnMap: typeof functionMap) => { 20 | forEach(fnMap, (path, functionName) => { 21 | if ( 22 | !process.env.FUNCTION_TARGET || 23 | process.env.FUNCTION_TARGET === functionName 24 | ) { 25 | module.exports[functionName] = require(path); 26 | } 27 | }); 28 | }; 29 | 30 | const fnMap = isDevelopment() 31 | ? { ...functionMap, ...devFunctionMap } 32 | : functionMap; 33 | 34 | loadFunctions(fnMap); 35 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/functions/src/publishers.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import * as functions from 'firebase-functions'; 3 | import admin from 'firebase-admin'; 4 | 5 | import { collectionName } from './services/mangarel/constants'; 6 | 7 | module.exports = functions 8 | .region('asia-northeast1') 9 | .https.onRequest(async (req, res) => { 10 | const snap = await admin 11 | .firestore() 12 | .collection(collectionName.publishers) 13 | .get(); 14 | const data = snap.docs.map(doc => doc.data()); 15 | res.send({ data }); 16 | }); 17 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/functions/src/search-books.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import * as functions from 'firebase-functions'; 3 | import admin from 'firebase-admin'; 4 | 5 | import { collectionName } from './services/mangarel/constants'; 6 | import { tokenize } from './utils/text-processor'; 7 | 8 | module.exports = functions 9 | .region(functions.config().locale.region) 10 | .https.onRequest(async (req, res) => { 11 | const searchWord = req.query.q; 12 | const booksRef = admin.firestore().collection(collectionName.books); 13 | 14 | let query = booksRef.limit(10); 15 | tokenize(searchWord).forEach(token => { 16 | query = query.where(`tokenMap.${token}`, '==', true); 17 | }); 18 | const snap = await query.get(); 19 | // const snap = await query.orderBy('publishedOn').get(); 20 | const data = snap.docs.map(doc => ({ 21 | id: doc.id, 22 | ...doc.data(), 23 | })); 24 | 25 | res.send({ data }); 26 | }); 27 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/functions/src/services/mangarel: -------------------------------------------------------------------------------- 1 | ../../../src/services/mangarel -------------------------------------------------------------------------------- /06-auth/mangarel-demo/functions/src/services/rakuten/models/book-item.ts: -------------------------------------------------------------------------------- 1 | export type BookItem = { 2 | title: string; 3 | titleKana: string; 4 | subTitle: string; 5 | subTitleKana: string; 6 | seriesName: string; 7 | seriesNameKana: string; 8 | contents: string; 9 | author: string; 10 | authorKana: string; 11 | publisherName: string; 12 | size: string; 13 | isbn: string; 14 | itemCaption: string; 15 | salesDate: string; 16 | itemPrice: number; 17 | listPrice: number; 18 | discountRate: number; 19 | discountPrice: number; 20 | itemUrl: string; 21 | affiliateUrl: string; 22 | smallImageUrl: string; 23 | mediumImageUrl: string; 24 | largeImageUrl: string; 25 | chirayomiUrl: string; 26 | availability: string; 27 | postageFlag: number; 28 | limitedFlag: number; 29 | reviewCount: number; 30 | reviewAverage: string; 31 | booksGenreId: string; 32 | }; 33 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/functions/src/utils: -------------------------------------------------------------------------------- 1 | ../../src/utils -------------------------------------------------------------------------------- /06-auth/mangarel-demo/functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "lib": ["dom", "esnext"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "noImplicitReturns": true, 8 | "noUnusedLocals": true, 9 | "outDir": "lib", 10 | "rootDir": "src", 11 | "removeComments": true, 12 | "resolveJsonModule": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "target": "es2017", 16 | "types": ["jest", "node"], 17 | "typeRoots": ["node_modules/@types", "../node_modules/@types"] 18 | }, 19 | "compileOnSave": true, 20 | "include": ["src"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/06-auth/mangarel-demo/public/favicon.ico -------------------------------------------------------------------------------- /06-auth/mangarel-demo/public/images/comingsoon-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/06-auth/mangarel-demo/public/images/comingsoon-large.png -------------------------------------------------------------------------------- /06-auth/mangarel-demo/public/images/comingsoon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/06-auth/mangarel-demo/public/images/comingsoon-small.png -------------------------------------------------------------------------------- /06-auth/mangarel-demo/public/mangarel-logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/06-auth/mangarel-demo/public/mangarel-logo192.png -------------------------------------------------------------------------------- /06-auth/mangarel-demo/public/mangarel-logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/06-auth/mangarel-demo/public/mangarel-logo512.png -------------------------------------------------------------------------------- /06-auth/mangarel-demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Mangarel", 3 | "name": "Mangarel 漫画発売情報アプリ", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "mangarel-logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "mangarel-logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#68e1e3", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .App-header { 12 | align-items: center; 13 | background-color: #282c34; 14 | color: white; 15 | display: flex; 16 | flex-direction: column; 17 | font-size: calc(10px + 2vmin); 18 | justify-content: center; 19 | min-height: 100vh; 20 | } 21 | 22 | .App-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes App-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | 31 | to { 32 | transform: rotate(360deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/Book/RakutenBooksButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useContext } from 'react'; 2 | import { isFuture } from 'date-fns'; 3 | 4 | import { ThemeContext } from 'contexts'; 5 | import { Book } from 'services/mangarel/models/book'; 6 | import LinkButton from 'components/common/atoms/LinkButton'; 7 | 8 | const RakutenBooksButton: FC<{ book: Book }> = ({ book }) => { 9 | const theme = useContext(ThemeContext); 10 | const linkText = 11 | book.publishedOn && isFuture(book.publishedOn.toDate()) 12 | ? '楽天ブックスで予約注文する' 13 | : '楽天ブックスの商品ページに行く'; 14 | 15 | return ( 16 | 20 | } 21 | link={`https://books.rakuten.co.jp/rb/${book.rbCode}/`} 22 | > 23 | {linkText} 24 | 25 | ); 26 | }; 27 | 28 | export default RakutenBooksButton; 29 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/Book/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import BookMain from 'containers/Book/BookMain'; 4 | 5 | const Home: React.FC = () => ( 6 |
7 | 8 |
9 | ); 10 | 11 | export default Home; 12 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/Home/Calendar.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import { Book } from 'services/mangarel/models/book'; 4 | import DividingHeader from 'components/common/header/DividingHeader'; 5 | import CalendarList from 'components/common/list/CalendarList'; 6 | import ListLoader from 'components/common/atoms/ListLoader'; 7 | 8 | type CalendarProps = { books: Book[]; loading?: boolean }; 9 | 10 | const Calendar: FC = ({ books, loading }) => ( 11 |
12 | 13 | もうすぐ発売の新刊 14 | 15 | {loading ? : } 16 |
17 | ); 18 | 19 | export default Calendar; 20 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Calendar from 'containers/Home/Calendar'; 4 | 5 | const Home: React.FC = () => ( 6 |
7 |
8 | 9 |
10 |
11 | ); 12 | 13 | export default Home; 14 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/Search/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import SearchUnit from 'containers/Search/SearchUnit'; 4 | 5 | const Search: React.FC = () => ( 6 |
7 | 8 |
9 | ); 10 | 11 | export default Search; 12 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/atoms/CardAttribute.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useContext } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Card from 'semantic-ui-react/dist/commonjs/views/Card'; 4 | import { ThemeContext } from 'contexts'; 5 | 6 | const CardAttribute: FC = ({ children, ...props }) => { 7 | const theme = useContext(ThemeContext); 8 | const Attribute = styled(Card.Meta)` 9 | &&& { 10 | color: ${theme.color.lightGray}; 11 | font-size: 0.66rem; 12 | text-align: right; 13 | } 14 | `; 15 | 16 | return {children}; 17 | }; 18 | 19 | export default CardAttribute; 20 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/atoms/CardContent.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useContext } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Card from 'semantic-ui-react/dist/commonjs/views/Card'; 4 | import { CardContentProps } from 'semantic-ui-react/dist/commonjs/views/Card/CardContent'; 5 | import { ThemeContext } from 'contexts'; 6 | 7 | const CardContent: FC = ({ children, ...props }) => { 8 | const theme = useContext(ThemeContext); 9 | const Content = styled(Card.Content)` 10 | &&& { 11 | color: ${theme.color.black}; 12 | padding: 0.55rem 0.75rem 0.4rem; 13 | } 14 | `; 15 | 16 | return {children}; 17 | }; 18 | 19 | export default CardContent; 20 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/atoms/CardDescription.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useContext } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Card from 'semantic-ui-react/dist/commonjs/views/Card'; 4 | import { CardDescriptionProps } from 'semantic-ui-react/dist/commonjs/views/Card/CardDescription'; 5 | import { ThemeContext } from 'contexts'; 6 | 7 | const CardDescription: FC = ({ children, ...props }) => { 8 | const theme = useContext(ThemeContext); 9 | const Desciption = styled(Card.Description)` 10 | &&& { 11 | color: ${theme.color.black}; 12 | font-size: 0.8rem; 13 | } 14 | 15 | .separator { 16 | margin: 0 0.05rem; 17 | } 18 | 19 | strong { 20 | margin: 0 0.15rem !important; 21 | } 22 | `; 23 | 24 | return {children}; 25 | }; 26 | 27 | export default CardDescription; 28 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/atoms/CardInfo.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useContext } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { ThemeContext } from 'contexts'; 4 | 5 | const CardInfo: FC = ({ children, ...props }) => { 6 | const theme = useContext(ThemeContext); 7 | const Info = styled.div` 8 | && { 9 | color: ${theme.color.black}; 10 | height: 3rem; 11 | line-height: 1.15rem; 12 | margin-top: 0.2rem; 13 | } 14 | `; 15 | 16 | return {children}; 17 | }; 18 | 19 | export default CardInfo; 20 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/atoms/CardSummary.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useContext } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Card from 'semantic-ui-react/dist/commonjs/views/Card'; 4 | import { CardDescriptionProps } from 'semantic-ui-react/dist/commonjs/views/Card/CardDescription'; 5 | import { ThemeContext } from 'contexts'; 6 | 7 | const CardDescription: FC = ({ children, ...props }) => { 8 | const theme = useContext(ThemeContext); 9 | const Desciption = styled(Card.Description)` 10 | &&& { 11 | color: ${theme.color.gray}; 12 | font-size: 0.55rem; 13 | line-height: 1rem; 14 | margin-top: 0.75rem; 15 | } 16 | `; 17 | 18 | return {children}; 19 | }; 20 | 21 | export default CardDescription; 22 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/atoms/CardTitle.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useContext } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Card from 'semantic-ui-react/dist/commonjs/views/Card'; 4 | import { CardHeaderProps } from 'semantic-ui-react/dist/commonjs/views/Card/CardHeader'; 5 | import { ThemeContext } from 'contexts'; 6 | 7 | const CardTitle: FC = ({ children, ...props }) => { 8 | const theme = useContext(ThemeContext); 9 | const Title = styled(Card.Header)` 10 | color: ${theme.color.black} !important; 11 | font-size: 0.9rem !important; 12 | font-weight: 600 !important; 13 | margin-top: 0.1rem !important; 14 | `; 15 | 16 | return {children}; 17 | }; 18 | 19 | export default CardTitle; 20 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/atoms/ItemCard.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import Card from 'semantic-ui-react/dist/commonjs/views/Card'; 3 | 4 | const ItemCard = styled(Card)` 5 | &&& { 6 | margin: 0.75rem 0 0; 7 | padding: 0; 8 | width: auto; 9 | } 10 | `; 11 | 12 | export default ItemCard; 13 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/atoms/LargeCoverImage.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import styled from '@emotion/styled'; 4 | import Image, { 5 | ImageProps, 6 | } from 'semantic-ui-react/dist/commonjs/elements/Image'; 7 | 8 | const ImageWrapper = styled.div` 9 | img { 10 | border: 1px #efefef solid; 11 | box-shadow: 0 1px 6px 1px rgba(0, 0, 0, 0.15); 12 | margin-bottom: 0 !important; 13 | } 14 | 15 | @media screen and (max-width: 1024px) { 16 | img { 17 | width: 300px !important; 18 | } 19 | } 20 | 21 | @media screen and (max-width: 480px) { 22 | img { 23 | width: 120px !important; 24 | } 25 | } 26 | `; 27 | 28 | const LargeCoverImage: FC = ({ children, ...props }) => ( 29 | 30 | 31 | {children} 32 | 33 | 34 | ); 35 | 36 | export default LargeCoverImage; 37 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/atoms/ListLoader.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Loader from 'semantic-ui-react/dist/commonjs/elements/Loader'; 4 | import Segment from 'semantic-ui-react/dist/commonjs/elements/Segment'; 5 | 6 | const LoaderWrapper = styled(Segment)` 7 | &&& { 8 | margin-top: 4rem; 9 | } 10 | `; 11 | 12 | const ListLoader: FC = () => ( 13 | 14 | 15 | 16 | ); 17 | 18 | export default ListLoader; 19 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/atoms/MainFrame.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import styled from '@emotion/styled'; 4 | import Container, { 5 | ContainerProps, 6 | } from 'semantic-ui-react/dist/commonjs/elements/Container'; 7 | 8 | const ContainerFrame = styled(Container)` 9 | && { 10 | margin-top: 1em; 11 | } 12 | `; 13 | 14 | const MainFrame: FC = ({ children, ...props }) => ( 15 | {children} 16 | ); 17 | 18 | export default MainFrame; 19 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/atoms/SmallCoverImage.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import styled from '@emotion/styled'; 4 | import Image, { 5 | ImageProps, 6 | } from 'semantic-ui-react/dist/commonjs/elements/Image'; 7 | 8 | const CoverImage = styled(Image)` 9 | filter: drop-shadow(2px 2px 2px rgba(0, 0, 0, 0.1)); 10 | margin-bottom: 0 !important; 11 | width: 55px !important; 12 | `; 13 | 14 | const SmallCoverImage: FC = ({ children, ...props }) => ( 15 | 16 | {children} 17 | 18 | ); 19 | 20 | export default SmallCoverImage; 21 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/atoms/Spacer.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | const Spacer: FC<{ height?: number }> = ({ height = 3 }) => { 5 | const SpacerSegment = styled.div` 6 | clear: both; 7 | height: ${height}rem; 8 | width: auto; 9 | `; 10 | 11 | return ; 12 | }; 13 | 14 | export default Spacer; 15 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/list/BookList.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | import { Book } from 'services/mangarel/models/book'; 5 | import BookCard from 'components/common/card/BookCard'; 6 | import ListLoader from 'components/common/atoms/ListLoader'; 7 | 8 | const ListWrapper = styled.div` 9 | margin: 1rem 0.5rem; 10 | `; 11 | 12 | const BookList: FC<{ books: Book[]; loading?: boolean }> = ({ 13 | books, 14 | loading = false, 15 | }) => 16 | loading ? ( 17 | 18 | ) : ( 19 | 20 | {books.map(book => ( 21 | 22 | ))} 23 | 24 | ); 25 | 26 | export default BookList; 27 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/list/CalendarList.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | import { Book } from 'services/mangarel/models/book'; 5 | import BookCard from 'components/common/card/BookCard'; 6 | 7 | const CalendarList: FC<{ books: Book[] }> = ({ books }) => { 8 | const ListWrapper = styled.div` 9 | margin: 1rem 0.5rem; 10 | `; 11 | 12 | return ( 13 | 14 | {books.map(book => ( 15 | 16 | ))} 17 | 18 | ); 19 | }; 20 | 21 | export default CalendarList; 22 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/work/AttributesField.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Grid from 'semantic-ui-react/dist/commonjs/collections/Grid'; 4 | 5 | const GridWrapper = styled.div` 6 | margin: 0.5rem 1rem 0.75rem; 7 | padding-top: 1rem; 8 | width: auto; 9 | `; 10 | 11 | const ButtonGroup: FC = ({ children }) => ( 12 | 13 | 14 | {children} 15 | 16 | 17 | ); 18 | 19 | export default ButtonGroup; 20 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/work/ButtonGroup.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | const Group = styled.div` 5 | padding-top: 1rem; 6 | width: auto; 7 | `; 8 | 9 | const ButtonGroup: FC = ({ children }) => {children}; 10 | 11 | export default ButtonGroup; 12 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/work/CardGroup.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | const Group = styled.div` 5 | padding: 0 0.11rem 0.2rem; 6 | width: auto; 7 | `; 8 | 9 | const CardGroup: FC = ({ children }) => {children}; 10 | 11 | export default CardGroup; 12 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/work/WorkAuthors.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | import React, { FC, useContext } from 'react'; 3 | import styled from '@emotion/styled'; 4 | 5 | import { ThemeContext } from 'contexts'; 6 | import { Author } from 'services/mangarel/models/author'; 7 | 8 | const WorkAuthors: FC<{ authors: Author[] | null }> = ({ authors }) => { 9 | const theme = useContext(ThemeContext); 10 | const AuthorInfo = styled.div` 11 | &&& { 12 | color: ${theme.color.gray}; 13 | font-size: 0.85rem; 14 | font-weight: 600; 15 | margin: 0 0 1rem !important; 16 | padding-top: 0 !important; 17 | } 18 | `; 19 | 20 | return ( 21 | 22 | {authors && 23 | authors.map(author => ( 24 | 25 | {author.name} 26 | 27 | ))} 28 | 29 | ); 30 | }; 31 | 32 | export default WorkAuthors; 33 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/work/WorkPlaceHolder.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Placeholder from 'semantic-ui-react/dist/commonjs/elements/Placeholder'; 4 | 5 | const MainPlaceholder = styled(Placeholder)` 6 | &&& { 7 | margin: 1rem 0.5rem; 8 | } 9 | 10 | .image.header::after { 11 | height: 11.5rem !important; 12 | margin-left: 8.5rem !important; 13 | } 14 | `; 15 | 16 | const WorkPlaceholder: FC = () => ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | 29 | export default WorkPlaceholder; 30 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/components/common/work/WorkTitle.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useContext } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Header from 'semantic-ui-react/dist/commonjs/elements/Header'; 4 | 5 | import { ThemeContext } from 'contexts'; 6 | 7 | type WorkTitleProps = { 8 | title: string; 9 | }; 10 | 11 | const WorkTitle: FC = ({ title }) => { 12 | const theme = useContext(ThemeContext); 13 | const Title = styled(Header)` 14 | &&& { 15 | color: ${theme.color.black}; 16 | font-size: 1rem; 17 | font-weight: 600; 18 | margin: 0.75rem 0; 19 | padding-top: 0.25rem; 20 | } 21 | `; 22 | 23 | return {title}; 24 | }; 25 | 26 | export default WorkTitle; 27 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/containers/Book/BookMain.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { useHistory, useParams } from 'react-router'; 3 | 4 | import useBook from 'hooks/use-book'; 5 | import BookMain from 'components/Book/BookMain'; 6 | import paths from 'paths'; 7 | 8 | const BookMainContainer: FC = () => { 9 | const history = useHistory(); 10 | const { bookId } = useParams(); 11 | if (!bookId) history.replace(paths.home); 12 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 13 | const { book } = useBook(bookId!); 14 | 15 | return book ? :
; 16 | }; 17 | 18 | export default BookMainContainer; 19 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/containers/Home/Calendar.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import Calendar from 'components/Home/Calendar'; 4 | import useBooks from 'hooks/use-books'; 5 | 6 | const CalendarContainer: FC = () => { 7 | const { books, loading } = useBooks({ limit: 50 }); 8 | 9 | return ; 10 | }; 11 | 12 | export default CalendarContainer; 13 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/containers/Search/SearchUnit.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, SyntheticEvent, useState } from 'react'; 2 | 3 | import useBookSearch from 'hooks/use-book-search'; 4 | import SearchForm from 'components/Search/SearchForm'; 5 | import BookList from 'components/common/list/BookList'; 6 | 7 | const SearchUnit: FC = () => { 8 | const [values, setValues] = useState({ q: '' }); 9 | const { books, loading } = useBookSearch(values.q, { limit: 20 }); 10 | 11 | const handleChange = ( 12 | targetName: string, 13 | newValue: string, 14 | event?: SyntheticEvent, 15 | ) => { 16 | if (event) event.persist(); 17 | 18 | setValues(v => ({ ...v, [targetName]: newValue })); 19 | }; 20 | 21 | return ( 22 |
23 | 24 | 25 |
26 | ); 27 | }; 28 | 29 | export default SearchUnit; 30 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/contexts.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import firebase from 'firebase/app'; 3 | 4 | import { createContext } from 'react'; 5 | import { User } from 'services/mangarel/models/user'; 6 | import mangarelTheme from './theme'; 7 | 8 | type FirebaseContextValue = { 9 | auth: firebase.auth.Auth | null; 10 | db: firebase.firestore.Firestore | null; 11 | }; 12 | 13 | export const FirebaseContext = createContext({ 14 | auth: null, 15 | db: null, 16 | }); 17 | 18 | type UserContextValue = { 19 | user: User | null; 20 | credential: firebase.auth.UserCredential | null; 21 | setCredential: (credential: firebase.auth.UserCredential | null) => void; 22 | }; 23 | 24 | export const UserContext = createContext({ 25 | user: null, 26 | credential: null, 27 | setCredential: () => undefined, 28 | }); 29 | 30 | export const ThemeContext = createContext( 31 | (null as unknown) as typeof mangarelTheme, 32 | ); 33 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/firebase-config.ts: -------------------------------------------------------------------------------- 1 | const firebaseConfig = { 2 | apiKey: process.env.REACT_APP_API_KEY, 3 | authDomain: process.env.REACT_APP_AUTH_DOMAIN, 4 | databaseURL: process.env.REACT_APP_DATABASE_URL, 5 | projectId: process.env.REACT_APP_PROJECT_ID, 6 | storageBucket: process.env.REACT_APP_STORAGE_BUCKET, 7 | messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID, 8 | }; 9 | 10 | export default firebaseConfig; 11 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 3 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 4 | sans-serif; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | margin: 0; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/paths.ts: -------------------------------------------------------------------------------- 1 | const paths = { 2 | book: '/book/:bookId([0-9]{13})', 3 | home: '/', 4 | search: '/search', 5 | signin: '/signin', 6 | }; 7 | 8 | export default paths; 9 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/services/mangarel/constants.ts: -------------------------------------------------------------------------------- 1 | export const collectionName = { 2 | authors: 'authors', 3 | books: 'books', 4 | users: 'users', 5 | publishers: 'publishers', 6 | docCounters: 'docCounters', 7 | feedMemos: 'feedMemos', 8 | } as const; 9 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/services/mangarel/find-user.ts: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase/app'; 2 | 3 | import { User } from './models/user'; 4 | import { collectionName } from './constants'; 5 | 6 | const findUser = async (db: firebase.firestore.Firestore, id: string) => { 7 | let theUser: User | null = null; 8 | const userDoc = await db 9 | .collection(collectionName.users) 10 | .doc(id) 11 | .get(); 12 | 13 | if (userDoc.exists) { 14 | const user = userDoc.data() as User; 15 | theUser = { ...user, id: userDoc.id }; 16 | } 17 | 18 | return theUser; 19 | }; 20 | 21 | export default findUser; 22 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/services/mangarel/models/author.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | 3 | export type Author = { 4 | id?: string; 5 | name: string; 6 | nameReading: string | null; 7 | variation: string; 8 | createdAt: firestore.Timestamp | null; 9 | updatedAt: firestore.Timestamp | null; 10 | }; 11 | 12 | export const blankAuthor: Author = { 13 | name: '', 14 | nameReading: null, 15 | variation: '', 16 | createdAt: null, 17 | updatedAt: null, 18 | }; 19 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/services/mangarel/models/book.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | import { Author } from './author'; 3 | import { Publisher } from './publisher'; 4 | 5 | export type Book = { 6 | id?: string; 7 | title: string; 8 | titleReading: string | null; 9 | publisherId: string; 10 | publisher: Publisher | null; 11 | authorIds: string[]; 12 | authors: Author[]; 13 | isbn: string; 14 | rbCode: string; 15 | hasImage: boolean; 16 | tokenMap: { [token: string]: boolean } | null; 17 | publishedOn: firestore.Timestamp | null; 18 | createdAt: firestore.Timestamp | null; 19 | updatedAt: firestore.Timestamp | null; 20 | }; 21 | 22 | export const blankBook: Book = { 23 | title: '', 24 | titleReading: null, 25 | publisherId: '', 26 | publisher: null, 27 | authorIds: [], 28 | authors: [], 29 | isbn: '', 30 | rbCode: '', 31 | hasImage: false, 32 | tokenMap: null, 33 | publishedOn: null, 34 | createdAt: null, 35 | updatedAt: null, 36 | }; 37 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/services/mangarel/models/feed-memo.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | 3 | export type FeedMemo = { 4 | id?: string; 5 | title: string | null; 6 | author: string | null; 7 | publisher: string | null; 8 | releaseDate: string | null; 9 | isbn: string | null; 10 | fetchedAt: firestore.Timestamp | null; 11 | createdAt: firestore.Timestamp | null; 12 | }; 13 | 14 | export const blankFeedMemo: FeedMemo = { 15 | title: null, 16 | author: null, 17 | publisher: null, 18 | releaseDate: null, 19 | isbn: null, 20 | fetchedAt: null, 21 | createdAt: null, 22 | }; 23 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/services/mangarel/models/publisher.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | 3 | export type Publisher = { 4 | id?: string; 5 | name: string; 6 | nameReading: string | null; 7 | website: string | null; 8 | createdAt: firestore.Timestamp | null; 9 | updatedAt: firestore.Timestamp | null; 10 | }; 11 | 12 | export const blankPublisher: Publisher = { 13 | name: '', 14 | nameReading: null, 15 | website: null, 16 | createdAt: null, 17 | updatedAt: null, 18 | }; 19 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/services/mangarel/models/user.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase/app'; 2 | 3 | export type User = { 4 | id?: string; 5 | screenName: string; 6 | displayName: string | null; 7 | description: string | null; 8 | photoUrl: string | null; 9 | provider: string; 10 | providerUid: string; 11 | createdAt: firestore.Timestamp | null; 12 | updatedAt: firestore.Timestamp | null; 13 | }; 14 | 15 | export const blankUser: User = { 16 | screenName: '', 17 | displayName: null, 18 | description: null, 19 | photoUrl: null, 20 | provider: 'twitter', 21 | providerUid: '', 22 | createdAt: null, 23 | updatedAt: null, 24 | }; 25 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/theme.ts: -------------------------------------------------------------------------------- 1 | const mangarelTheme = { 2 | color: { 3 | primary: '#68e1e3', 4 | secondary: '#ff6394', 5 | tertiary: '#84ca66', 6 | link: '#18c2c8', 7 | white: '#ffffff', 8 | black: '#1e1e2a', 9 | gray: '#808080', 10 | lightGray: '#979797', 11 | indigo: '#5cb2cc', 12 | ultraLightGray: '#e4e4e4', 13 | amazon: '#f08705', 14 | google: '#1a73e8', 15 | rakuten: '#d86060', 16 | }, 17 | }; 18 | 19 | export default mangarelTheme; 20 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/utils/env.ts: -------------------------------------------------------------------------------- 1 | export const isDevelopment = () => (process.env.node || '').includes('nodenv'); 2 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/utils/n-gram.spec.ts: -------------------------------------------------------------------------------- 1 | import { bigram, trigram } from './n-gram'; 2 | 3 | describe('Get n-grams', () => { 4 | describe('Bigram', () => { 5 | it('should be devided successfully', () => { 6 | const text = '終末のワルキューレ'; 7 | const result = bigram(text); 8 | expect(result).toEqual([ 9 | '終末', 10 | '末の', 11 | 'のワ', 12 | 'ワル', 13 | 'ルキ', 14 | 'キュ', 15 | 'ュー', 16 | 'ーレ', 17 | ]); 18 | }); 19 | }); 20 | 21 | describe('Trigram', () => { 22 | it('should be devided successfully', () => { 23 | const text = '終末のワルキューレ'; 24 | const result = trigram(text); 25 | expect(result).toEqual([ 26 | '終末の', 27 | '末のワ', 28 | 'のワル', 29 | 'ワルキ', 30 | 'ルキュ', 31 | 'キュー', 32 | 'ューレ', 33 | ]); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/utils/n-gram.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This function is ported from https://www.npmjs.com/package/n-gram 3 | * Copyright (c) 2014 Titus Wormer 4 | * (The MIT License) 5 | */ 6 | export const nGram = (n: number) => { 7 | if (n < 1 || n === Infinity) { 8 | throw new Error(`${n} is not a valid argument for n-gram`); 9 | } 10 | 11 | return (value: string | null) => { 12 | const nGrams: string[] = []; 13 | let index = 0; 14 | 15 | if (!value) { 16 | return nGrams; 17 | } 18 | 19 | index = value.length - n + 1; 20 | 21 | if (index < 1) { 22 | return nGrams; 23 | } 24 | 25 | // eslint-disable-next-line no-plusplus 26 | while (index--) { 27 | nGrams[index] = value.slice(index, index + n); 28 | } 29 | 30 | return nGrams; 31 | }; 32 | }; 33 | 34 | export const bigram = nGram(2); 35 | export const trigram = nGram(3); 36 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/src/utils/timer.ts: -------------------------------------------------------------------------------- 1 | export const sleep = (msec: number) => 2 | new Promise(resolve => setTimeout(() => resolve(), msec)); 3 | -------------------------------------------------------------------------------- /06-auth/mangarel-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react", 17 | "baseUrl": "src", 18 | "incremental": true 19 | }, 20 | "include": ["src"], 21 | "exclude": ["node_modules", "build", "scripts", "functions"] 22 | } 23 | -------------------------------------------------------------------------------- /samples/react-firebase-sample.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oukayuka/ReactFirebaseBook/323f0693980905b4a0c85a6d35324076b8f3dae1/samples/react-firebase-sample.pdf --------------------------------------------------------------------------------