├── 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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
--------------------------------------------------------------------------------