├── .eslintignore ├── .eslintrc.json ├── .firebaserc ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── build.yml ├── .gitignore ├── .prettierrc.json ├── Dockerfile ├── README.md ├── docker-compose.yml ├── dummy.firebase.admin.key.json ├── dummy.firebase.client.key.json ├── firebase.json ├── lerna.json ├── package.json ├── packages ├── app │ ├── .gitignore │ ├── capacitor.config.json │ ├── ionic.config.json │ ├── package.json │ ├── public │ │ ├── assets │ │ │ └── shapes.svg │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── App.test.tsx │ │ ├── App.tsx │ │ ├── components │ │ │ ├── ExploreContainer.css │ │ │ ├── ExploreContainer.tsx │ │ │ ├── atoms │ │ │ │ └── .gitkeep │ │ │ ├── global │ │ │ │ ├── .gitkeep │ │ │ │ ├── Authentication │ │ │ │ │ └── index.tsx │ │ │ │ └── Authorization │ │ │ │ │ └── index.tsx │ │ │ ├── moleclues │ │ │ │ └── .gitkeep │ │ │ └── organisms │ │ │ │ └── .gitkeep │ │ ├── firebase │ │ │ └── client.ts │ │ ├── index.tsx │ │ ├── modules │ │ │ ├── auth.ts │ │ │ ├── entities │ │ │ │ └── .gitkeep │ │ │ ├── feedback.ts │ │ │ ├── reducer.ts │ │ │ ├── repositories │ │ │ │ ├── auth.ts │ │ │ │ ├── httpClient.ts │ │ │ │ ├── index.ts │ │ │ │ └── sample.ts │ │ │ ├── services │ │ │ │ └── .gitkeep │ │ │ └── table.ts │ │ ├── pages │ │ │ ├── SignIn.tsx │ │ │ ├── Tab1.css │ │ │ ├── Tab1.tsx │ │ │ ├── Tab2.css │ │ │ ├── Tab2.tsx │ │ │ ├── Tab3.css │ │ │ └── Tab3.tsx │ │ ├── react-app-env.d.ts │ │ ├── serviceWorker.ts │ │ ├── setupTests.ts │ │ ├── store.ts │ │ └── theme │ │ │ └── variables.css │ └── tsconfig.json ├── backend-graphql │ ├── .gitignore │ ├── README.md │ ├── nest-cli.json │ ├── nodemon-debug.json │ ├── nodemon.json │ ├── package.json │ ├── src │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── main.ts │ │ ├── schema.graphql │ │ └── user │ │ │ ├── dto │ │ │ └── user.input.ts │ │ │ ├── user.entity.ts │ │ │ ├── user.module.ts │ │ │ ├── user.resolver.spec.ts │ │ │ └── user.resolver.ts │ ├── test │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── backend │ ├── .env.tmpl │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── nest-cli.json │ ├── nodemon-debug.json │ ├── nodemon.json │ ├── package.json │ ├── src │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.dto.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── auth │ │ │ ├── auth.controller.spec.ts │ │ │ ├── auth.controller.ts │ │ │ ├── auth.decorator.ts │ │ │ ├── auth.guard.ts │ │ │ ├── auth.module.ts │ │ │ ├── auth.service.spec.ts │ │ │ └── auth.service.ts │ │ ├── firebase │ │ │ └── index.ts │ │ ├── main.ts │ │ ├── pipes │ │ │ └── validation.ts │ │ └── user │ │ │ ├── user.entity.ts │ │ │ ├── user.module.ts │ │ │ ├── user.service.spec.ts │ │ │ └── user.service.ts │ ├── test │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── electron │ ├── .babelrc │ ├── .gitignore │ ├── .storybook │ │ ├── addons.js │ │ ├── config.js │ │ └── presets.js │ ├── configs │ │ ├── builder │ │ │ ├── builder.config.base.ts │ │ │ ├── builder.mac.local.ts │ │ │ ├── builder.mac.prod.ts │ │ │ └── builder.win.prod.ts │ │ └── webpack │ │ │ ├── constants.babel.ts │ │ │ ├── webpack.config.base.ts │ │ │ ├── webpack.config.dev.babel.ts │ │ │ ├── webpack.config.prod.babel.ts │ │ │ ├── webpack.config.server.babel.ts │ │ │ └── webpack.config.stg.babel.ts │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── main │ │ │ └── index.ts │ │ └── renderer │ │ │ ├── components │ │ │ ├── atoms │ │ │ │ ├── TextForm │ │ │ │ │ ├── TextForm.spec.tsx │ │ │ │ │ ├── TextForm.stories.tsx │ │ │ │ │ ├── TextForm.tsx │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── TextForm.spec.tsx.snap │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ ├── nano │ │ │ │ ├── Icon │ │ │ │ │ ├── Icon.stories.tsx │ │ │ │ │ ├── Icon.tsx │ │ │ │ │ ├── SvgComponents │ │ │ │ │ │ ├── Sample.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── svgs │ │ │ │ │ │ └── sample.svg │ │ │ │ │ ├── template.js │ │ │ │ │ └── tsx │ │ │ │ │ │ ├── Sample.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ └── index.ts │ │ │ └── organisms │ │ │ │ ├── LoginForm │ │ │ │ ├── LoginForm.tsx │ │ │ │ └── index.ts │ │ │ │ ├── Sample │ │ │ │ ├── Sample.tsx │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ ├── index.html │ │ │ ├── index.tsx │ │ │ ├── lib │ │ │ └── http │ │ │ │ ├── api │ │ │ │ ├── index.ts │ │ │ │ ├── postSignIn.spec.ts │ │ │ │ └── postSignIn.ts │ │ │ │ ├── client │ │ │ │ ├── __tests__ │ │ │ │ │ ├── get.test.ts │ │ │ │ │ └── index.test.ts │ │ │ │ ├── get.ts │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ └── states │ │ │ ├── index.ts │ │ │ ├── interfaces.ts │ │ │ ├── modules │ │ │ ├── auth │ │ │ │ ├── __tests__ │ │ │ │ │ ├── actions.spec.ts │ │ │ │ │ ├── operations │ │ │ │ │ │ └── signIn.spec.ts │ │ │ │ │ ├── reducers.test.ts │ │ │ │ │ └── selectors.spec.ts │ │ │ │ ├── actionTypes.ts │ │ │ │ ├── actions.ts │ │ │ │ ├── factories.ts │ │ │ │ ├── index.ts │ │ │ │ ├── operations.ts │ │ │ │ ├── reducers.ts │ │ │ │ └── selectors.ts │ │ │ ├── global │ │ │ │ ├── __tests__ │ │ │ │ │ ├── actions.test.ts │ │ │ │ │ ├── reducers.test.ts │ │ │ │ │ └── selectors.test.ts │ │ │ │ ├── actionTypes.ts │ │ │ │ ├── actions.ts │ │ │ │ ├── factory.ts │ │ │ │ ├── index.ts │ │ │ │ ├── operations.ts │ │ │ │ ├── reducers.ts │ │ │ │ └── selectors.ts │ │ │ └── router │ │ │ │ ├── actions.ts │ │ │ │ ├── index.ts │ │ │ │ ├── operations.ts │ │ │ │ └── selectors.ts │ │ │ ├── store.ts │ │ │ └── test-utils.ts │ └── tsconfig.json ├── firebase-admin │ ├── .gitignore │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── firebase-client │ ├── .gitignore │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── fixtures │ ├── .gitignore │ ├── package.json │ ├── src │ │ ├── auth │ │ │ ├── cookie.ts │ │ │ ├── hooks.ts │ │ │ ├── index.ts │ │ │ └── middleware.ts │ │ ├── index.ts │ │ ├── swr-cache-path.ts │ │ ├── types │ │ │ └── next.d.ts │ │ └── utility │ │ │ ├── hooks.ts │ │ │ ├── index.ts │ │ │ └── ssr.ts │ └── tsconfig.json ├── graphql │ ├── .gitignore │ ├── codegen.yml │ ├── package.json │ ├── src │ │ ├── fragments │ │ │ └── user.graphql │ │ ├── index.ts │ │ ├── mutations │ │ │ └── createUser.graphql │ │ ├── queries │ │ │ └── listUsers.graphql │ │ └── react-apollo │ │ │ └── generated.tsx │ └── tsconfig.json ├── interface │ ├── .gitignore │ ├── package.json │ ├── src │ │ ├── entity │ │ │ ├── claims.ts │ │ │ ├── index.ts │ │ │ └── user.ts │ │ ├── index.ts │ │ └── request │ │ │ ├── auth.ts │ │ │ ├── base.ts │ │ │ └── index.ts │ └── tsconfig.json ├── web-antd │ ├── .babelrc │ ├── .env.tmpl │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── polyfills.js │ ├── src │ │ ├── assets │ │ │ └── antd-custom.less │ │ ├── pages │ │ │ ├── _document.tsx │ │ │ ├── about.tsx │ │ │ ├── api │ │ │ │ └── validate.tsx │ │ │ ├── index.tsx │ │ │ └── sign_in.tsx │ │ └── types │ │ │ └── next.d.ts │ └── tsconfig.json ├── web-graphql │ ├── .babelrc │ ├── .env.tmpl │ ├── next-env.d.ts │ ├── next.config.js │ ├── nodemon.json │ ├── package.json │ ├── src │ │ ├── components │ │ │ └── thema.ts │ │ ├── factory.ts │ │ ├── firebase │ │ │ ├── admin.ts │ │ │ └── client.ts │ │ ├── fixtures │ │ │ ├── auth │ │ │ │ ├── cookie.ts │ │ │ │ ├── hooks.ts │ │ │ │ ├── middleware.ts │ │ │ │ └── swr-cache-path.ts │ │ │ ├── http-client │ │ │ │ └── index.ts │ │ │ ├── utility │ │ │ │ ├── hooks.ts │ │ │ │ ├── index.ts │ │ │ │ └── ssr.ts │ │ │ └── withApollo │ │ │ │ └── index.tsx │ │ ├── pages │ │ │ ├── _app.tsx │ │ │ ├── _document.tsx │ │ │ ├── about.tsx │ │ │ ├── index.tsx │ │ │ └── sign_in.tsx │ │ ├── run.ts │ │ ├── static │ │ │ ├── img │ │ │ │ ├── icon192.png │ │ │ │ └── icon512.png │ │ │ └── manifest.json │ │ ├── tsconfig.server.json │ │ └── types │ │ │ └── next.d.ts │ └── tsconfig.json ├── web-swr │ ├── .babelrc │ ├── .env.tmpl │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── polyfills.js │ ├── src │ │ ├── pages │ │ │ ├── _document.tsx │ │ │ ├── about.tsx │ │ │ ├── api │ │ │ │ └── validate.tsx │ │ │ ├── index.tsx │ │ │ └── sign_in.tsx │ │ └── types │ │ │ └── next.d.ts │ └── tsconfig.json └── web │ ├── .babelrc │ ├── .env.tmpl │ ├── Dockerfile │ ├── next-env.d.ts │ ├── next.config.js │ ├── nodemon.json │ ├── package.json │ ├── src │ ├── components │ │ ├── atoms │ │ │ ├── .gitkeep │ │ │ ├── Button.tsx │ │ │ ├── CircularIndeterminate.tsx │ │ │ ├── Link.tsx │ │ │ ├── SnackbarContent.tsx │ │ │ └── index.ts │ │ ├── global │ │ │ ├── AppBar.tsx │ │ │ ├── Feedback.tsx │ │ │ └── index.tsx │ │ ├── moleclues │ │ │ ├── .gitkeep │ │ │ ├── Progress.tsx │ │ │ └── Snackbar.tsx │ │ ├── organisms │ │ │ └── .gitkeep │ │ └── theme.ts │ ├── factory.ts │ ├── firebase │ │ ├── admin.ts │ │ ├── client.ts │ │ └── interface.ts │ ├── index.ts │ ├── modules │ │ ├── auth.ts │ │ ├── entities │ │ │ └── .gitkeep │ │ ├── feedback.ts │ │ ├── reducer.ts │ │ ├── repositories │ │ │ ├── auth.ts │ │ │ ├── httpClient.ts │ │ │ ├── index.ts │ │ │ └── sample.ts │ │ ├── services │ │ │ ├── auth.ts │ │ │ ├── index.ts │ │ │ └── routing.ts │ │ └── table.ts │ ├── pages │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ ├── about.tsx │ │ ├── elb-health-check.tsx │ │ ├── index.tsx │ │ ├── login_required.tsx │ │ └── sign_in.tsx │ ├── run.ts │ ├── static │ │ ├── img │ │ │ ├── icon192.png │ │ │ └── icon512.png │ │ └── manifest.json │ ├── store.ts │ ├── tsconfig.server.json │ └── types │ │ └── next.d.ts │ ├── thema.ts │ └── tsconfig.json ├── renovate.json ├── tsconfig.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules/** 2 | **/dist/** 3 | **/cjs/** 4 | **/esm/** -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true, 6 | "jest": true 7 | }, 8 | "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:prettier/recommended"], 9 | "globals": { 10 | "Atomics": "readonly", 11 | "SharedArrayBuffer": "readonly" 12 | }, 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "sourceType": "module", 16 | "project": "./tsconfig.json", 17 | "tsconfigRootDir": ".", 18 | "ecmaFeatures": { 19 | "tsx": true 20 | } 21 | }, 22 | "plugins": ["@typescript-eslint", "react", "react-hooks"], 23 | "rules": { 24 | "no-unused-vars": "off", 25 | "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], 26 | "react/prop-types": "off", 27 | "react-hooks/rules-of-hooks": "error", 28 | "react-hooks/exhaustive-deps": "warn", 29 | "require-atomic-updates": "off", 30 | "no-console": "off", 31 | "prettier/prettier": "warn" 32 | }, 33 | "settings": { 34 | "react": { 35 | "version": "detect" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "abyssparanoia-f6d72" 4 | }, 5 | "targets": { 6 | "abyssparanoia-f6d72": { 7 | "hosting": { 8 | "ionic": [ 9 | "abyssparanoia-f6d72" 10 | ] 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Proposed Changes 2 | 3 | ## Implementation 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | node-version: [12.x] 12 | 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | 20 | - name: prepare file 21 | run: | 22 | cp packages/web/.env.tmpl packages/web/.env 23 | cp dummy.firebase.client.key.json packages/web/firebase.client.key.json 24 | cp dummy.firebase.admin.key.json packages/web/firebase.admin.key.json 25 | cp dummy.firebase.client.key.json packages/app/src/firebase/firebase.client.key.json 26 | cp dummy.firebase.client.key.json packages/web-graphql/firebase.client.key.json 27 | cp dummy.firebase.client.key.json packages/firebase-client/firebase.client.key.json 28 | 29 | - name: install deps 30 | run: yarn 31 | 32 | - name: lint 33 | run: yarn lint 34 | 35 | - name: build 36 | run: yarn build 37 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false, 4 | "printWidth": 120, 5 | "trailingComma": "none", 6 | "arrowParens": "avoid" 7 | } 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine 2 | 3 | RUN mkdir -p /app 4 | ADD . /app 5 | WORKDIR /app 6 | 7 | RUN yarn 8 | RUN yarn build 9 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | web: &node-server 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | ports: 9 | - 3000:3000 10 | volumes: 11 | - .:. 12 | command: ash -c "yarn workspace @abyssparanoia/web start:dev" 13 | 14 | backend: &node-server 15 | build: 16 | context: . 17 | dockerfile: Dockerfile 18 | ports: 19 | - 3001:3001 20 | volumes: 21 | - .:. 22 | command: ash -c "yarn workspace @abyssparanoia/backend start:dev" 23 | -------------------------------------------------------------------------------- /dummy.firebase.admin.key.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "service_account", 3 | "project_id": "project_id", 4 | "private_key_id": "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd", 5 | "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAwJENcRev+eXZKvhhWLiV3Lz2MvO+naQRHo59g3vaNQnbgyduN/L4krlr\nJ5c6FiikXdtJNb/QrsAHSyJWCu8j3T9CruiwbidGAk2W0RuViTVspjHUTsIHExx9euWM0Uom\nGvYkoqXahdhPL/zViVSJt+Rt8bHLsMvpb8RquTIb9iKY3SMV2tCofNmyCSgVbghq/y7lKORt\nV/IRguWs6R22fbkb0r2MCYoNAbZ9dqnbRIFNZBC7itYtUoTEresRWcyFMh0zfAIJycWOJlVL\nDLqkY2SmIx8u7fuysCg1wcoSZoStuDq02nZEMw1dx8HGzE0hynpHlloRLByuIuOAfMCCYwID\nAQABAoIBADFtihu7TspAO0wSUTpqttzgC/nsIsNn95T2UjVLtyjiDNxPZLUrwq42tdCFur0x\nVW9Z+CK5x6DzXWvltlw8IeKKeF1ZEOBVaFzy+YFXKTz835SROcO1fgdjyrme7lRSShGlmKW/\nGKY+baUNquoDLw5qreXaE0SgMp0jt5ktyYuVxvhLDeV4omw2u6waoGkifsGm8lYivg5l3VR7\nw2IVOvYZTt4BuSYVwOM+qjwaS1vtL7gv0SUjrj85Ja6zERRdFiITDhZw6nsvacr9/+/aut9E\naL/koSSb62g5fntQMEwoT4hRnjPnAedmorM9Rhddh2TB3ZKTBbMN1tUk3fJxOuECgYEA+z6l\neSaAcZ3qvwpntcXSpwwJ0SSmzLTH2RJNf+Ld3eBHiSvLTG53dWB7lJtF4R1KcIwf+KGcOFJv\nsnepzcZBylRvT8RrAAkV0s9OiVm1lXZyaepbLg4GGFJBPi8A6VIAj7zYknToRApdW0s1x/XX\nChewfJDckqsevTMovdbg8YkCgYEAxDYX+3mfvv/opo6HNNY3SfVunM+4vVJL+n8gWZ2w9kz3\nQ9Ub9YbRmI7iQaiVkO5xNuoG1n9bM+3Mnm84aQ1YeNT01YqeyQsipP5Wi+um0PzYTaBw9RO+\n8Gh6992OwlJiRtFk5WjalNWOxY4MU0ImnJwIfKQlUODvLmcixm68NYsCgYEAuAqI3jkk55Vd\nKvotREsX5wP7gPePM+7NYiZ1HNQL4Ab1f/bTojZdTV8Sx6YCR0fUiqMqnE+OBvfkGGBtw22S\nLesx6sWf99Ov58+x4Q0U5dpxL0Lb7d2Z+2Dtp+Z4jXFjNeeI4ae/qG/LOR/b0pE0J5F415ap\n7Mpq5v89vepUtrkCgYAjMXytu4v+q1Ikhc4UmRPDrUUQ1WVSd+9u19yKlnFGTFnRjej86hiw\nH3jPxBhHra0a53EgiilmsBGSnWpl1WH4EmJz5vBCKUAmjgQiBrueIqv9iHiaTNdjsanUyaWw\njyxXfXl2eI80QPXh02+8g1H/pzESgjK7Rg1AqnkfVH9nrwKBgQDJVxKBPTw9pigYMVt9iHrR\niCl9zQVjRMbWiPOc0J56+/5FZYm/AOGl9rfhQ9vGxXZYZiOP5FsNkwt05Y1UoAAH4B4VQwbL\nqod71qOcI0ywgZiIR87CYw40gzRfjWnN+YEEW1qfyoNLilEwJB8iB/T+ZePHGmJ4MmQ/cTn9\nxpdLXA==\n-----END RSA PRIVATE KEY-----\n", 6 | "client_email": "foo@project_id.iam.gserviceaccount.com" 7 | } 8 | -------------------------------------------------------------------------------- /dummy.firebase.client.key.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiKey": "AIzaSyBKu1YQwebiQTU2lkdEy70sB0QfIsample", 3 | "authDomain": "sample.firebaseapp.com", 4 | "databaseURL": "https://sample.firebaseio.com", 5 | "projectId": "sample", 6 | "storageBucket": "sample.appspot.com", 7 | "messagingSenderId": "963212943052" 8 | } 9 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": [ 3 | { 4 | "target": "ionic", 5 | "public": "./packages/app/build", 6 | "ignore": ["firebase.json", "**/.*", "**/node_modules/**", "packages/bff/**", "packages/web/**"], 7 | "predeploy": ["yarn workspace @abyssparanoia/app build"], 8 | "rewrites": [ 9 | { 10 | "source": "**", 11 | "destination": "/index.html" 12 | } 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "changelog": { 3 | "cacheDir": ".changelog", 4 | "labels": { 5 | "Type: Breaking Change": ":boom: Type: Breaking Change", 6 | "Type: Bug": ":bug: Type: Bug", 7 | "Type: Documentation ": ":memo: Documentation", 8 | "Type: Feature": ":rocket: Type: Feature" 9 | } 10 | }, 11 | "command": { 12 | "version": { 13 | "allowBranch": ["master", "release"] 14 | } 15 | }, 16 | "npmClient": "yarn", 17 | "packages": ["packages/*"], 18 | "useWorkspaces": true, 19 | "version": "0.0.1" 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-boilerplate", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "start": "lerna run start --stream", 7 | "build": "lerna run build --include-dependencies", 8 | "lint": "eslint --fix -c ./.eslintrc.json './packages/**/*.{ts,tsx}'", 9 | "test": "lerna run test", 10 | "clean": "lerna run clean && lerna clean --y" 11 | }, 12 | "devDependencies": { 13 | "@types/jest": "24.9.1", 14 | "@types/supertest": "2.0.11", 15 | "@typescript-eslint/eslint-plugin": "3.10.1", 16 | "@typescript-eslint/parser": "3.10.1", 17 | "concurrently": "6.4.0", 18 | "eslint": "6.8.0", 19 | "eslint-config-prettier": "8.3.0", 20 | "eslint-plugin-prettier": "3.4.1", 21 | "eslint-plugin-react": "7.27.0", 22 | "eslint-plugin-react-hooks": "4.3.0", 23 | "husky": "7.0.4", 24 | "jest": "24.9.0", 25 | "lerna": "4.0.0", 26 | "lint-staged": "11.2.6", 27 | "nodemon": "2.0.15", 28 | "npm-run-all": "4.1.5", 29 | "prettier": "2.4.1", 30 | "rimraf": "3.0.2", 31 | "serve": "12.0.1", 32 | "ts-jest": "26.5.6", 33 | "ts-node": "10.4.0", 34 | "tsconfig-paths": "3.11.0", 35 | "typescript": "4.9.5", 36 | "wait-on": "6.0.0" 37 | }, 38 | "private": true, 39 | "workspaces": [ 40 | "packages/*" 41 | ], 42 | "husky": { 43 | "hooks": { 44 | "pre-commit": "lint-staged" 45 | } 46 | }, 47 | "lint-staged": { 48 | "*.{.ts,tsx}": [ 49 | "yarn lint", 50 | "git add" 51 | ] 52 | }, 53 | "dependencies": { 54 | "simple-git": "3.16.0" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | .vscode 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /packages/app/capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "io.ionic.starter", 3 | "appName": "app", 4 | "bundledWebRuntime": false, 5 | "npmClient": "yarn", 6 | "webDir": "build", 7 | "cordova": {} 8 | } 9 | -------------------------------------------------------------------------------- /packages/app/ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "integrations": { 4 | "capacitor": {} 5 | }, 6 | "type": "react" 7 | } 8 | -------------------------------------------------------------------------------- /packages/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@abyssparanoia/app", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start:dev": "PORT=3002 react-scripts start", 7 | "start:prod": "serve -s build", 8 | "build": "react-scripts build", 9 | "test": "react-scripts test", 10 | "eject": "react-scripts eject" 11 | }, 12 | "dependencies": { 13 | "@abyssparanoia/interface": "0.0.1", 14 | "@capacitor/core": "3.9.0", 15 | "@ionic/react": "5.8.5", 16 | "@ionic/react-router": "5.8.5", 17 | "axios": "0.24.0", 18 | "connected-react-router": "6.9.1", 19 | "firebase": "7.24.0", 20 | "ionicons": "5.5.4", 21 | "query-string": "7.0.1", 22 | "react": "17.0.2", 23 | "react-dom": "17.0.2", 24 | "react-redux": "7.2.6", 25 | "react-router": "5.2.1", 26 | "react-router-dom": "5.3.0", 27 | "redux": "4.2.1", 28 | "redux-thunk": "2.4.0", 29 | "styled-components": "5.3.3", 30 | "typescript-fsa": "3.0.0", 31 | "typescript-fsa-reducers": "1.2.2" 32 | }, 33 | "devDependencies": { 34 | "@babel/helper-compilation-targets": "7.12.5", 35 | "@capacitor/cli": "3.9.0", 36 | "@testing-library/jest-dom": "5.15.0", 37 | "@testing-library/react": "12.1.2", 38 | "@testing-library/user-event": "13.5.0", 39 | "@types/node": "14.17.33", 40 | "@types/react": "17.0.35", 41 | "@types/react-dom": "17.0.11", 42 | "@types/react-redux": "7.1.20", 43 | "@types/react-router": "5.1.17", 44 | "@types/react-router-dom": "5.3.2", 45 | "@types/redux-logger": "3.0.9", 46 | "@types/styled-components": "5.1.15", 47 | "babel-loader": "8.1.0", 48 | "react-scripts": "4.0.3", 49 | "redux-devtools-extension": "2.13.9", 50 | "redux-logger": "3.0.6", 51 | "webpack": "4.42.0", 52 | "webpack-dev-server": "3.11.3" 53 | }, 54 | "browserslist": { 55 | "production": [ 56 | ">0.2%", 57 | "not dead", 58 | "not op_mini all" 59 | ], 60 | "development": [ 61 | "last 1 chrome version", 62 | "last 1 firefox version", 63 | "last 1 safari version" 64 | ] 65 | }, 66 | "description": "An Ionic project" 67 | } 68 | -------------------------------------------------------------------------------- /packages/app/public/assets/shapes.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ionic App 6 | 7 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /packages/app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Ionic App", 3 | "name": "My Ionic App", 4 | "icons": [ 5 | { 6 | "src": "assets/icon/favicon.png", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "assets/icon/icon.png", 12 | "type": "image/png", 13 | "sizes": "512x512", 14 | "purpose": "maskable" 15 | } 16 | ], 17 | "start_url": ".", 18 | "display": "standalone", 19 | "theme_color": "#ffffff", 20 | "background_color": "#ffffff" 21 | } 22 | -------------------------------------------------------------------------------- /packages/app/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from '@testing-library/react' 3 | import App from './App' 4 | 5 | test('renders without crashing', () => { 6 | const { baseElement } = render() 7 | expect(baseElement).toBeDefined() 8 | }) 9 | -------------------------------------------------------------------------------- /packages/app/src/components/ExploreContainer.css: -------------------------------------------------------------------------------- 1 | .container { 2 | text-align: center; 3 | position: absolute; 4 | left: 0; 5 | right: 0; 6 | top: 50%; 7 | transform: translateY(-50%); 8 | } 9 | 10 | .container strong { 11 | font-size: 20px; 12 | line-height: 26px; 13 | } 14 | 15 | .container p { 16 | font-size: 16px; 17 | line-height: 22px; 18 | color: #8c8c8c; 19 | margin: 0; 20 | } 21 | 22 | .container a { 23 | text-decoration: none; 24 | } -------------------------------------------------------------------------------- /packages/app/src/components/ExploreContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './ExploreContainer.css' 3 | 4 | interface ContainerProps { 5 | name: string 6 | } 7 | 8 | const ExploreContainer: React.FC = ({ name }) => { 9 | return ( 10 |
11 | {name} 12 |

13 | Explore{' '} 14 | 15 | UI Components 16 | 17 |

18 |
19 | ) 20 | } 21 | 22 | export default ExploreContainer 23 | -------------------------------------------------------------------------------- /packages/app/src/components/atoms/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abyssparanoia/nextjs-firebase-boilerplate/fa5c6a0967ea7801341df1e7babd2da2574330c2/packages/app/src/components/atoms/.gitkeep -------------------------------------------------------------------------------- /packages/app/src/components/global/.gitkeep: -------------------------------------------------------------------------------- 1 | ] -------------------------------------------------------------------------------- /packages/app/src/components/global/Authentication/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { subscribeIdToken, unsubscribe } from '../../../modules/auth' 4 | import { useEffect } from 'react' 5 | 6 | export const Authentication: React.FunctionComponent = ({ children }) => { 7 | const dispatch = useDispatch() 8 | 9 | useEffect(() => { 10 | dispatch(subscribeIdToken()) 11 | 12 | return () => { 13 | dispatch(unsubscribe()) 14 | } 15 | // eslint-disable-next-line react-hooks/exhaustive-deps 16 | }, []) 17 | 18 | return <>{children} 19 | } 20 | -------------------------------------------------------------------------------- /packages/app/src/components/global/Authorization/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { useSelector } from 'react-redux' 3 | import { ReduxStore } from '../../../modules/reducer' 4 | import { useHistory } from 'react-router' 5 | 6 | export const Authorization: React.FunctionComponent = ({ children }) => { 7 | const { firebaseUser, isLoading } = useSelector(({ auth: { firebaseUser, isLoading } }: ReduxStore) => ({ 8 | firebaseUser, 9 | isLoading 10 | })) 11 | const history = useHistory() 12 | 13 | useEffect(() => { 14 | if (firebaseUser === null) { 15 | history.push('/sign_in') 16 | return 17 | } 18 | }, [firebaseUser, history, isLoading]) 19 | 20 | return <>{children} 21 | } 22 | -------------------------------------------------------------------------------- /packages/app/src/components/moleclues/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abyssparanoia/nextjs-firebase-boilerplate/fa5c6a0967ea7801341df1e7babd2da2574330c2/packages/app/src/components/moleclues/.gitkeep -------------------------------------------------------------------------------- /packages/app/src/components/organisms/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abyssparanoia/nextjs-firebase-boilerplate/fa5c6a0967ea7801341df1e7babd2da2574330c2/packages/app/src/components/organisms/.gitkeep -------------------------------------------------------------------------------- /packages/app/src/firebase/client.ts: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase/app' 2 | import 'firebase/auth' 3 | 4 | if (!firebase.apps.length) { 5 | firebase.initializeApp(require('./firebase.client.key.json')) 6 | } 7 | 8 | const auth = firebase.auth() 9 | 10 | class FirebaseAuthenticationError extends Error { 11 | constructor(error: firebase.auth.Error) { 12 | super(error.message) 13 | this.name = new.target.name 14 | Object.setPrototypeOf(this, new.target.prototype) 15 | 16 | // https://github.com/firebase/firebase-js-sdk/blob/master/packages/auth/src/error_auth.js 17 | switch (error.code) { 18 | case 'auth/email-already-in-use': 19 | this.message = '入力されたメールアドレスはすでに使用されています。' 20 | break 21 | case 'auth/invalid-email': 22 | this.message = '不正なメールアドレスです' 23 | break 24 | case 'auth/user-not-found': 25 | this.message = 'ユーザーが見つかりませんでした' 26 | break 27 | case 'auth/wrong-password': 28 | this.message = 'パスワードが一致しません' 29 | break 30 | default: 31 | this.message = 'エラーが発生しました' 32 | break 33 | } 34 | } 35 | } 36 | 37 | export { firebase, auth, FirebaseAuthenticationError } 38 | -------------------------------------------------------------------------------- /packages/app/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | import * as serviceWorker from './serviceWorker' 5 | 6 | ReactDOM.render(, document.getElementById('root')) 7 | 8 | // If you want your app to work offline and load faster, you can change 9 | // unregister() to register() below. Note this comes with some pitfalls. 10 | // Learn more about service workers: https://bit.ly/CRA-PWA 11 | serviceWorker.unregister() 12 | -------------------------------------------------------------------------------- /packages/app/src/modules/entities/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abyssparanoia/nextjs-firebase-boilerplate/fa5c6a0967ea7801341df1e7babd2da2574330c2/packages/app/src/modules/entities/.gitkeep -------------------------------------------------------------------------------- /packages/app/src/modules/feedback.ts: -------------------------------------------------------------------------------- 1 | import actionCreatorFactory from 'typescript-fsa' 2 | import { reducerWithInitialState } from 'typescript-fsa-reducers' 3 | 4 | const actionCreator = actionCreatorFactory('feedback') 5 | 6 | export interface Feedback { 7 | id: string 8 | variant: 'success' | 'warning' | 'error' | 'info' 9 | message: string 10 | } 11 | 12 | const actions = { 13 | pushFeedback: actionCreator>('PUSH_FEEDBACK'), 14 | popFeedback: actionCreator<{ id: string }>('POP_FEEDBACK') 15 | } 16 | 17 | export interface State { 18 | list: Feedback[] 19 | } 20 | 21 | const initialState: State = { 22 | list: [] 23 | } 24 | 25 | export const pushFeedback = actions.pushFeedback 26 | 27 | export const popFeedback = actions.popFeedback 28 | 29 | export const reducer = reducerWithInitialState(initialState) 30 | .case(actions.pushFeedback, (state, payload) => { 31 | const id = Math.random().toString(36).substring(2, 15) 32 | 33 | return { 34 | ...state, 35 | list: [{ id, ...payload }, ...state.list] 36 | } 37 | }) 38 | .case(actions.popFeedback, (state, payload) => ({ 39 | ...state, 40 | list: state.list.filter(feedback => feedback.id !== payload.id) 41 | })) 42 | .build() 43 | -------------------------------------------------------------------------------- /packages/app/src/modules/reducer.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { State as TableState, reducer as tableReducer } from './table' 3 | import { State as AuthState, reducer as authReducer } from './auth' 4 | import { State as FeedbackState, reducer as feedbackReducer } from './feedback' 5 | import { connectRouter, RouterState } from 'connected-react-router' 6 | import { History } from 'history' 7 | 8 | export interface ReduxStore { 9 | router: RouterState 10 | table: TableState 11 | auth: AuthState 12 | feedback: FeedbackState 13 | } 14 | 15 | export const createRootReducer = (history: History) => 16 | combineReducers({ 17 | router: connectRouter(history), 18 | table: tableReducer, 19 | auth: authReducer, 20 | feedback: feedbackReducer 21 | }) 22 | -------------------------------------------------------------------------------- /packages/app/src/modules/repositories/auth.ts: -------------------------------------------------------------------------------- 1 | import { auth, firebase } from '../../firebase/client' 2 | import { HttpClient } from './httpClient' 3 | import { SignInResponse, Claims } from '@abyssparanoia/interface' 4 | 5 | export const getToken = () => { 6 | const currentUser = auth.currentUser 7 | if (!currentUser) { 8 | throw new Error('Unauthenticated') 9 | } 10 | return currentUser.getIdToken() 11 | } 12 | 13 | export const signInWithGoogle = async () => { 14 | await auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()) 15 | return signIn() 16 | } 17 | 18 | interface ISignInWithEmailAndPassword { 19 | email: string 20 | password: string 21 | } 22 | 23 | export const signInWithEmailAndPassword = ({ email, password }: ISignInWithEmailAndPassword) => 24 | auth.signInWithEmailAndPassword(email, password) 25 | 26 | const signIn = async () => { 27 | const { 28 | data: { user, customToken } 29 | } = await new HttpClient({ 30 | url: `${process.env.REACT_APP_API_HOST}/auth/sign_in`, 31 | token: await getToken() 32 | }).post({}) 33 | 34 | await auth.signInWithCustomToken(customToken) 35 | 36 | const { claims } = await auth.currentUser!.getIdTokenResult() 37 | return { user, claims: claims as Claims } 38 | } 39 | 40 | export const signOut = async () => { 41 | await auth.signOut() 42 | } 43 | -------------------------------------------------------------------------------- /packages/app/src/modules/repositories/index.ts: -------------------------------------------------------------------------------- 1 | export * from './httpClient' 2 | export * from './auth' 3 | export * from './sample' 4 | -------------------------------------------------------------------------------- /packages/app/src/modules/repositories/sample.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from './httpClient' 2 | import { SignInRequest } from '@abyssparanoia/interface' 3 | 4 | export const samplePost = async () => { 5 | const req: SignInRequest['body'] = { 6 | userID: 'userID', 7 | password: 'password' 8 | } 9 | const res = await new HttpClient({ url: `http://localhost:3001` }).post(req) 10 | console.log(res.data) 11 | } 12 | -------------------------------------------------------------------------------- /packages/app/src/modules/services/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abyssparanoia/nextjs-firebase-boilerplate/fa5c6a0967ea7801341df1e7babd2da2574330c2/packages/app/src/modules/services/.gitkeep -------------------------------------------------------------------------------- /packages/app/src/modules/table.ts: -------------------------------------------------------------------------------- 1 | import actionCreatorFactory from 'typescript-fsa' 2 | import { reducerWithInitialState } from 'typescript-fsa-reducers' 3 | import { Dispatch } from 'redux' 4 | 5 | const timeout = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) 6 | 7 | const actionCreator = actionCreatorFactory('table') 8 | 9 | export const actions = { 10 | fetchTableList: actionCreator.async('list') 11 | } 12 | 13 | export interface State { 14 | list: string[] 15 | isLoading: boolean 16 | error?: Error 17 | } 18 | 19 | const initialState: State = { 20 | list: [], 21 | isLoading: false 22 | } 23 | 24 | export const fetchTableList = () => async (dispatch: Dispatch) => { 25 | dispatch(actions.fetchTableList.started()) 26 | await timeout(1500) 27 | // mock data 28 | const list = ['users', 'posts', 'post_favorites'] 29 | return dispatch(actions.fetchTableList.done({ result: { list } })) 30 | } 31 | 32 | export const reducer = reducerWithInitialState(initialState) 33 | .case(actions.fetchTableList.started, state => ({ 34 | ...state, 35 | isLoading: true 36 | })) 37 | .case(actions.fetchTableList.done, (state, payload) => ({ ...state, isLoading: false, list: payload.result.list })) 38 | .case(actions.fetchTableList.failed, (state, payload) => ({ ...state, isLoading: false, error: payload.error })) 39 | .build() 40 | -------------------------------------------------------------------------------- /packages/app/src/pages/SignIn.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react' 2 | import { IonButton, IonContent } from '@ionic/react' 3 | import { useDispatch } from 'react-redux' 4 | import { signInWithGoogle } from '../modules/auth' 5 | 6 | export const SignIn = () => { 7 | const dispatch = useDispatch() 8 | const handleGoogleSignIn = useCallback(() => { 9 | dispatch(signInWithGoogle()) 10 | }, [dispatch]) 11 | 12 | return ( 13 | 14 | 15 | SignIn with Google 16 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /packages/app/src/pages/Tab1.css: -------------------------------------------------------------------------------- 1 | ion-content ion-toolbar { 2 | --background: translucent; 3 | } -------------------------------------------------------------------------------- /packages/app/src/pages/Tab1.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react' 2 | import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonButton } from '@ionic/react' 3 | import ExploreContainer from '../components/ExploreContainer' 4 | import './Tab1.css' 5 | import { useDispatch } from 'react-redux' 6 | import { signOut } from '../modules/auth' 7 | 8 | const Tab1: React.FC = () => { 9 | const dispatch = useDispatch() 10 | 11 | const handleSignOut = useCallback(() => { 12 | dispatch(signOut()) 13 | }, [dispatch]) 14 | 15 | return ( 16 | 17 | 18 | 19 | Tab 1 20 | 21 | 22 | 23 | 24 | 25 | Tab 1 26 | 27 | 28 | 29 | Sign Out 30 | 31 | 32 | ) 33 | } 34 | 35 | export default Tab1 36 | -------------------------------------------------------------------------------- /packages/app/src/pages/Tab2.css: -------------------------------------------------------------------------------- 1 | ion-content ion-toolbar { 2 | --background: translucent; 3 | } -------------------------------------------------------------------------------- /packages/app/src/pages/Tab2.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react' 3 | import ExploreContainer from '../components/ExploreContainer' 4 | import './Tab2.css' 5 | 6 | const Tab2: React.FC = () => { 7 | return ( 8 | 9 | 10 | 11 | Tab 2 12 | 13 | 14 | 15 | 16 | 17 | Tab 2 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | } 25 | 26 | export default Tab2 27 | -------------------------------------------------------------------------------- /packages/app/src/pages/Tab3.css: -------------------------------------------------------------------------------- 1 | ion-content ion-toolbar { 2 | --background: translucent; 3 | } -------------------------------------------------------------------------------- /packages/app/src/pages/Tab3.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react' 3 | import ExploreContainer from '../components/ExploreContainer' 4 | import './Tab3.css' 5 | 6 | const Tab3: React.FC = () => { 7 | return ( 8 | 9 | 10 | 11 | Tab 3 12 | 13 | 14 | 15 | 16 | 17 | Tab 3 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | } 25 | 26 | export default Tab3 27 | -------------------------------------------------------------------------------- /packages/app/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/app/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect' 6 | -------------------------------------------------------------------------------- /packages/app/src/store.ts: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, Store } from 'redux' 2 | import { createRootReducer } from './modules/reducer' 3 | import { History } from 'history' 4 | import { composeWithDevTools } from 'redux-devtools-extension' 5 | import { logger } from 'redux-logger' 6 | import thunk from 'redux-thunk' 7 | import { routerMiddleware } from 'connected-react-router' 8 | 9 | // export const history = createBrowserHistory() 10 | 11 | export const makeStore = (history: History): Store => 12 | createStore( 13 | createRootReducer(history), 14 | undefined, 15 | process.env.NODE_ENV !== 'production' 16 | ? composeWithDevTools(applyMiddleware(routerMiddleware(history), logger, thunk)) 17 | : applyMiddleware(routerMiddleware(history), thunk) 18 | ) 19 | -------------------------------------------------------------------------------- /packages/app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": false, 6 | "strictNullChecks": true, 7 | "strictPropertyInitialization": true, 8 | "skipLibCheck": true, 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "noEmit": true, 18 | "jsx": "react" 19 | }, 20 | "include": ["src"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/backend-graphql/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json -------------------------------------------------------------------------------- /packages/backend-graphql/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "ts", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src" 5 | } 6 | -------------------------------------------------------------------------------- /packages/backend-graphql/nodemon-debug.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "ignore": ["src/**/*.spec.ts"], 5 | "exec": "node --inspect-brk -r ts-node/register -r tsconfig-paths/register src/main.ts" 6 | } 7 | -------------------------------------------------------------------------------- /packages/backend-graphql/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["dist"], 3 | "ext": "js", 4 | "exec": "node dist/main" 5 | } 6 | -------------------------------------------------------------------------------- /packages/backend-graphql/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@abyssparanoia/backend-graphql", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "tsc -p tsconfig.build.json", 9 | "format": "prettier --write \"src/**/*.ts\"", 10 | "start": "ts-node -r tsconfig-paths/register src/main.ts", 11 | "start:dev": "concurrently --handle-input \"wait-on dist/main.js && nodemon\" \"tsc -w -p tsconfig.build.json\" ", 12 | "start:debug": "nodemon --config nodemon-debug.json", 13 | "prestart:prod": "rimraf dist && npm run build", 14 | "start:prod": "node dist/main.js", 15 | "lint": "tslint -p tsconfig.json -c tslint.json", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@nestjs/common": "7.6.18", 24 | "@nestjs/core": "7.6.18", 25 | "@nestjs/graphql": "8.0.2", 26 | "@nestjs/platform-express": "7.6.18", 27 | "apollo-server-express": "2.25.3", 28 | "class-transformer": "0.4.0", 29 | "class-validator": "0.14.0", 30 | "graphql": "15.7.2", 31 | "graphql-tools": "8.2.0", 32 | "reflect-metadata": "0.1.13", 33 | "rimraf": "3.0.2", 34 | "rxjs": "7.4.0", 35 | "type-graphql": "1.1.1" 36 | }, 37 | "devDependencies": { 38 | "@nestjs/testing": "7.6.18", 39 | "@types/express": "4.17.13", 40 | "@types/node": "14.17.33" 41 | }, 42 | "jest": { 43 | "moduleFileExtensions": [ 44 | "js", 45 | "json", 46 | "ts" 47 | ], 48 | "rootDir": "src", 49 | "testRegex": ".spec.ts$", 50 | "transform": { 51 | "^.+\\.(t|j)s$": "ts-jest" 52 | }, 53 | "coverageDirectory": "../coverage", 54 | "testEnvironment": "node" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/backend-graphql/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing' 2 | import { AppController } from './app.controller' 3 | import { AppService } from './app.service' 4 | 5 | describe('AppController', () => { 6 | let appController: AppController 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService] 12 | }).compile() 13 | 14 | appController = app.get(AppController) 15 | }) 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!') 20 | }) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /packages/backend-graphql/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common' 2 | import { AppService } from './app.service' 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/backend-graphql/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { GraphQLModule } from '@nestjs/graphql' 3 | import { UserModule } from './user/user.module' 4 | 5 | @Module({ 6 | imports: [ 7 | GraphQLModule.forRoot({ 8 | playground: true, 9 | autoSchemaFile: './src/schema.graphql' 10 | }), 11 | UserModule 12 | ] 13 | }) 14 | export class AppModule {} 15 | -------------------------------------------------------------------------------- /packages/backend-graphql/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!' 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/backend-graphql/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core' 2 | import { AppModule } from './app.module' 3 | 4 | async function bootstrap() { 5 | const app = await NestFactory.create(AppModule) 6 | await app.listen(3003) 7 | } 8 | bootstrap() 9 | -------------------------------------------------------------------------------- /packages/backend-graphql/src/schema.graphql: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------- 2 | # !!! THIS FILE WAS GENERATED BY TYPE-GRAPHQL !!! 3 | # !!! DO NOT MODIFY THIS FILE BY YOURSELF !!! 4 | # ----------------------------------------------- 5 | 6 | type Mutation { 7 | createUser(param: UserCreateInput!): User! 8 | } 9 | 10 | type Query { 11 | list: [User!]! 12 | } 13 | 14 | type User { 15 | id: ID! 16 | name: String! 17 | } 18 | 19 | input UserCreateInput { 20 | name: String! 21 | } 22 | -------------------------------------------------------------------------------- /packages/backend-graphql/src/user/dto/user.input.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator' 2 | import { Field, InputType } from 'type-graphql' 3 | import { User } from '../user.entity' 4 | 5 | @InputType() 6 | export class UserCreateInput implements Partial { 7 | @Field() 8 | @IsNotEmpty() 9 | readonly name: string 10 | } 11 | -------------------------------------------------------------------------------- /packages/backend-graphql/src/user/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { Field, ID, ObjectType } from 'type-graphql' 2 | 3 | @ObjectType() 4 | export class User { 5 | @Field(() => ID) 6 | id: string 7 | 8 | @Field() 9 | name: string 10 | } 11 | -------------------------------------------------------------------------------- /packages/backend-graphql/src/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { UserResolver } from './user.resolver' 3 | 4 | @Module({ 5 | providers: [UserResolver] 6 | }) 7 | export class UserModule {} 8 | -------------------------------------------------------------------------------- /packages/backend-graphql/src/user/user.resolver.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing' 2 | import { UserResolver } from './user.resolver' 3 | 4 | describe('UserResolver', () => { 5 | let resolver: UserResolver 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [UserResolver] 10 | }).compile() 11 | 12 | resolver = module.get(UserResolver) 13 | }) 14 | 15 | it('should be defined', () => { 16 | expect(resolver).toBeDefined() 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /packages/backend-graphql/src/user/user.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Resolver, Query, Args, Mutation } from '@nestjs/graphql' 2 | import { User } from './user.entity' 3 | import { UserCreateInput } from './dto/user.input' 4 | 5 | const list = [ 6 | { 7 | id: '1', 8 | name: 'Aさん' 9 | }, 10 | { 11 | id: '2', 12 | name: 'Bさん' 13 | } 14 | ] 15 | 16 | @Resolver('User') 17 | export class UserResolver { 18 | @Query(() => [User]) 19 | async list(): Promise { 20 | return list 21 | } 22 | 23 | @Mutation(() => User) 24 | async createUser(@Args('param') param: UserCreateInput): Promise { 25 | return { id: 'aaaa', name: param.name } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/backend-graphql/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing' 2 | import * as request from 'supertest' 3 | import { AppModule } from './../src/app.module' 4 | 5 | describe('AppController (e2e)', () => { 6 | let app 7 | 8 | beforeEach(async () => { 9 | const moduleFixture: TestingModule = await Test.createTestingModule({ 10 | imports: [AppModule] 11 | }).compile() 12 | 13 | app = moduleFixture.createNestApplication() 14 | await app.init() 15 | }) 16 | 17 | it('/ (GET)', () => { 18 | return request(app.getHttpServer()).get('/').expect(200).expect('Hello World!') 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /packages/backend-graphql/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/backend-graphql/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/backend-graphql/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "allowSyntheticDefaultImports": true, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "module": "commonjs", 8 | "outDir": "./dist", 9 | "noFallthroughCasesInSwitch": true, 10 | "noImplicitAny": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noUnusedLocals": true, 14 | "strictPropertyInitialization": false, 15 | "noUnusedParameters": true, 16 | "removeComments": true, 17 | "sourceMap": true, 18 | "strict": true, 19 | "strictNullChecks": true, 20 | "target": "es2017", 21 | "skipLibCheck": true, 22 | "types": ["jest", "node"] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/backend/.env.tmpl: -------------------------------------------------------------------------------- 1 | GOOGLE_APPLICATION_CREDENTIALS="./firebase.admin.key.json" -------------------------------------------------------------------------------- /packages/backend/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json -------------------------------------------------------------------------------- /packages/backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine 2 | 3 | RUN mkdir -p /app 4 | ADD . /app 5 | WORKDIR /app 6 | 7 | RUN yarn 8 | RUN yarn build 9 | 10 | 11 | ENV PORT 3001 12 | EXPOSE 3001 13 | 14 | ENTRYPOINT ["yarn", "workspace", "@abyssparanoia/backend","start:prod"] -------------------------------------------------------------------------------- /packages/backend/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "ts", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src" 5 | } 6 | -------------------------------------------------------------------------------- /packages/backend/nodemon-debug.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "ignore": ["src/**/*.spec.ts"], 5 | "exec": "node --inspect-brk -r ts-node/register -r tsconfig-paths/register src/main.ts" 6 | } 7 | -------------------------------------------------------------------------------- /packages/backend/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["dist"], 3 | "ext": "js", 4 | "exec": "node dist/main" 5 | } 6 | -------------------------------------------------------------------------------- /packages/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@abyssparanoia/backend", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "", 6 | "author": "", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc -p tsconfig.build.json", 10 | "format": "prettier --write \"src/**/*.ts\"", 11 | "start:dev": "concurrently --handle-input \"wait-on dist/main.js && nodemon\" \"tsc -w -p tsconfig.build.json\" ", 12 | "start:debug": "nodemon --config nodemon-debug.json", 13 | "start:prod": "node dist/main.js", 14 | "test": "jest", 15 | "test:watch": "jest --watch", 16 | "test:cov": "jest --coverage", 17 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 18 | "test:e2e": "jest --config ./test/jest-e2e.json", 19 | "clean": "rimraf dist" 20 | }, 21 | "dependencies": { 22 | "@abyssparanoia/interface": "0.0.1", 23 | "@nestjs/common": "7.6.18", 24 | "@nestjs/config": "1.1.0", 25 | "@nestjs/core": "7.6.18", 26 | "@nestjs/platform-express": "7.6.18", 27 | "class-transformer": "0.4.0", 28 | "class-validator": "0.14.0", 29 | "dotenv": "10.0.0", 30 | "firebase-admin": "9.12.0", 31 | "reflect-metadata": "0.1.13", 32 | "rimraf": "3.0.2", 33 | "rxjs": "7.4.0" 34 | }, 35 | "devDependencies": { 36 | "@nestjs/testing": "7.6.18", 37 | "@types/express": "4.17.13", 38 | "@types/node": "14.17.33" 39 | }, 40 | "jest": { 41 | "moduleFileExtensions": [ 42 | "js", 43 | "json", 44 | "ts" 45 | ], 46 | "rootDir": "src", 47 | "testRegex": ".spec.ts$", 48 | "transform": { 49 | "^.+\\.(t|j)s$": "ts-jest" 50 | }, 51 | "coverageDirectory": "../coverage", 52 | "testEnvironment": "node" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/backend/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing' 2 | import { AppController } from './app.controller' 3 | import { AppService } from './app.service' 4 | 5 | describe('AppController', () => { 6 | let appController: AppController 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService] 12 | }).compile() 13 | 14 | appController = app.get(AppController) 15 | }) 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!') 20 | }) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /packages/backend/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body } from '@nestjs/common' 2 | import { AppService } from './app.service' 3 | import { SignInRequestDto } from './app.dto' 4 | 5 | @Controller() 6 | export class AppController { 7 | constructor(private readonly appService: AppService) {} 8 | 9 | @Get() 10 | getHello(): string { 11 | return this.appService.getHello() 12 | } 13 | 14 | @Post() 15 | async signIn(@Body() signInRequestDto: SignInRequestDto) { 16 | // eslint-disable-next-line no-console 17 | console.log(signInRequestDto) 18 | return { 19 | accessToken: 'accessToken', 20 | refreshToken: 'refreshToken' 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/backend/src/app.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString } from 'class-validator' 2 | import { SignInRequest, ExtractPropertyType } from '@abyssparanoia/interface' 3 | 4 | export class SignInRequestDto implements ExtractPropertyType { 5 | @IsString() 6 | userID!: string 7 | 8 | @IsString() 9 | password!: string 10 | } 11 | -------------------------------------------------------------------------------- /packages/backend/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { AppController } from './app.controller' 3 | import { AppService } from './app.service' 4 | import { UserModule } from './user/user.module' 5 | import { AuthModule } from './auth/auth.module' 6 | import { ConfigModule } from '@nestjs/config' 7 | 8 | @Module({ 9 | imports: [UserModule, AuthModule, ConfigModule], 10 | controllers: [AppController], 11 | providers: [AppService] 12 | }) 13 | export class AppModule {} 14 | -------------------------------------------------------------------------------- /packages/backend/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!' 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/backend/src/auth/auth.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing' 2 | import { AuthController } from './auth.controller' 3 | import { AuthService } from './auth.service' 4 | import { UserService } from '../user/user.service' 5 | 6 | describe('Auth Controller', () => { 7 | let controller: AuthController 8 | 9 | beforeEach(async () => { 10 | const module: TestingModule = await Test.createTestingModule({ 11 | controllers: [AuthController], 12 | providers: [AuthService, UserService] 13 | }).compile() 14 | 15 | controller = module.get(AuthController) 16 | }) 17 | 18 | it('should be defined', () => { 19 | expect(controller).toBeDefined() 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /packages/backend/src/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Post, UseGuards, NotFoundException } from '@nestjs/common' 2 | import { AuthService } from './auth.service' 3 | import { AuthUser, IAuthUser } from './auth.decorator' 4 | import { AuthGuard } from './auth.guard' 5 | import { SignInResponse } from '@abyssparanoia/interface' 6 | import { UserService } from '../user/user.service' 7 | 8 | @Controller('auth') 9 | export class AuthController { 10 | constructor(private readonly authService: AuthService, private readonly userService: UserService) {} 11 | 12 | @Post('/sign_in') 13 | @UseGuards(AuthGuard) 14 | async signIn(@AuthUser() { uid }: IAuthUser): Promise { 15 | const user = await this.userService.get(uid) 16 | if (!user) { 17 | throw new NotFoundException(`user not found`) 18 | } 19 | const customToken = await this.authService.createCustomToken(uid, { role: user.role }) 20 | 21 | return { user, customToken } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/backend/src/auth/auth.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator } from '@nestjs/common' 2 | import { Claims } from '@abyssparanoia/interface' 3 | 4 | export type IAuthUser = Claims & { uid: string } 5 | 6 | export const AuthUser = createParamDecorator( 7 | (_, req): IAuthUser => { 8 | return { ...req.claims } 9 | } 10 | ) 11 | -------------------------------------------------------------------------------- /packages/backend/src/auth/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common' 2 | import { auth } from '../firebase' 3 | 4 | @Injectable() 5 | export class AuthGuard implements CanActivate { 6 | async canActivate(context: ExecutionContext): Promise { 7 | const req = context.switchToHttp().getRequest() 8 | const { authorization } = req.headers 9 | if (!authorization || !authorization.startsWith('Bearer ')) { 10 | return false 11 | } 12 | const idToken = authorization.slice(7, authorization.length) 13 | 14 | const decodedIdToken = await auth.verifyIdToken(idToken).catch(err => { 15 | console.error(err) 16 | throw new ForbiddenException(`${err.message}`) 17 | }) 18 | 19 | // @ts-ignore 20 | req.claims = { ...decodedIdToken } 21 | 22 | return true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/backend/src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { AuthService } from './auth.service' 3 | import { AuthController } from './auth.controller' 4 | import { UserService } from '../user/user.service' 5 | 6 | @Module({ 7 | providers: [AuthService, UserService], 8 | controllers: [AuthController] 9 | }) 10 | export class AuthModule {} 11 | -------------------------------------------------------------------------------- /packages/backend/src/auth/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing' 2 | import { AuthService } from './auth.service' 3 | 4 | describe('AuthService', () => { 5 | let service: AuthService 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [AuthService] 10 | }).compile() 11 | 12 | service = module.get(AuthService) 13 | }) 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined() 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /packages/backend/src/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { auth } from '../firebase' 3 | import { Claims } from '@abyssparanoia/interface' 4 | 5 | @Injectable() 6 | export class AuthService { 7 | async createCustomToken(uid: string, claims: Claims) { 8 | return auth.createCustomToken(uid, claims) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/backend/src/firebase/index.ts: -------------------------------------------------------------------------------- 1 | import * as admin from 'firebase-admin' 2 | require('dotenv').config() 3 | 4 | const app = process.env.GCLOUD_PROJECT 5 | ? admin.initializeApp() 6 | : admin.initializeApp({ 7 | credential: admin.credential.applicationDefault() 8 | }) 9 | 10 | const auth = app.auth() 11 | const firestore = app.firestore() 12 | 13 | export { auth, firestore } 14 | -------------------------------------------------------------------------------- /packages/backend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core' 2 | import { AppModule } from './app.module' 3 | import { ValidationPipe } from './pipes/validation' 4 | import { Logger } from '@nestjs/common' 5 | import { Request as ExpressRequest, Response as ExpressResponse } from 'express' 6 | require('dotenv').config() 7 | 8 | function requestLogger(logger: Logger): (req: ExpressRequest, res: ExpressResponse, next: () => void) => void { 9 | return (req, res, next): void => { 10 | res.on('finish', (): void => { 11 | logger.debug(`${req.method} ${req.url} -> ${res.statusCode}`) 12 | }) 13 | next() 14 | } 15 | } 16 | 17 | async function bootstrap() { 18 | const app = await NestFactory.create(AppModule, { logger: ['debug', 'log', 'verbose', 'warn', 'error'] }) 19 | 20 | const logger = new Logger() 21 | app.useLogger(logger) 22 | app.use(requestLogger(logger)) 23 | 24 | app.useGlobalPipes(new ValidationPipe()) 25 | app.enableCors() 26 | await app.listen(3001) 27 | } 28 | bootstrap() 29 | -------------------------------------------------------------------------------- /packages/backend/src/pipes/validation.ts: -------------------------------------------------------------------------------- 1 | import { PipeTransform, ArgumentMetadata, BadRequestException, HttpStatus, Injectable } from '@nestjs/common' 2 | import { validate, ValidationError } from 'class-validator' 3 | import { plainToClass } from 'class-transformer' 4 | import { HttpException } from '@nestjs/common/exceptions/http.exception' 5 | 6 | @Injectable() 7 | export class ValidationPipe implements PipeTransform { 8 | async transform(value: object, metadata: ArgumentMetadata) { 9 | if (!value) { 10 | throw new BadRequestException('No data submitted') 11 | } 12 | 13 | const { metatype } = metadata 14 | if (!metatype || !this.toValidate(metatype)) { 15 | return value 16 | } 17 | const object = plainToClass(metatype, value) 18 | const errors = await validate(object) 19 | if (errors.length > 0) { 20 | throw new HttpException( 21 | { message: 'Input data validation failed', errors: this.buildError(errors) }, 22 | HttpStatus.BAD_REQUEST 23 | ) 24 | } 25 | return value 26 | } 27 | 28 | private buildError(errors: ValidationError[]) { 29 | const result: { 30 | [key: string]: any 31 | } = {} 32 | errors.forEach(el => { 33 | let prop = el.property 34 | Object.entries(el.constraints || {}).forEach(constraint => { 35 | result[prop + constraint[0]] = `${constraint[1]}` 36 | }) 37 | }) 38 | return result 39 | } 40 | 41 | private toValidate(metatype: String | Boolean | Number | Array | Object): boolean { 42 | const types = [String, Boolean, Number, Array, Object] 43 | return !types.find(type => metatype === type) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/backend/src/user/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { firestore } from 'firebase-admin' 2 | import { User } from '@abyssparanoia/interface' 3 | 4 | export const newUserFromDsnp = (dsnp: firestore.DocumentSnapshot): User => { 5 | const data = dsnp.data() as Omit 6 | 7 | return { id: dsnp.id, ...data } 8 | } 9 | -------------------------------------------------------------------------------- /packages/backend/src/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { UserService } from './user.service' 3 | 4 | @Module({ 5 | providers: [UserService] 6 | }) 7 | export class UserModule {} 8 | -------------------------------------------------------------------------------- /packages/backend/src/user/user.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing' 2 | import { UserService } from './user.service' 3 | 4 | describe('UserService', () => { 5 | let service: UserService 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [UserService] 10 | }).compile() 11 | 12 | service = module.get(UserService) 13 | }) 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined() 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /packages/backend/src/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { firestore } from '../firebase' 3 | import { newUserFromDsnp } from './user.entity' 4 | 5 | @Injectable() 6 | export class UserService { 7 | async get(userID: string) { 8 | const dsnp = await firestore.collection('users').doc(userID).get() 9 | if (!dsnp.exists || !dsnp.data()) { 10 | return undefined 11 | } 12 | return newUserFromDsnp(dsnp) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/backend/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing' 2 | import * as request from 'supertest' 3 | import { AppModule } from './../src/app.module' 4 | 5 | describe('AppController (e2e)', () => { 6 | let app 7 | 8 | beforeEach(async () => { 9 | const moduleFixture: TestingModule = await Test.createTestingModule({ 10 | imports: [AppModule] 11 | }).compile() 12 | 13 | app = moduleFixture.createNestApplication() 14 | await app.init() 15 | }) 16 | 17 | it('/ (GET)', () => { 18 | return request(app.getHttpServer()).get('/').expect(200).expect('Hello World!') 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /packages/backend/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/backend/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "allowSyntheticDefaultImports": true, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "module": "commonjs", 8 | "outDir": "./dist", 9 | "noFallthroughCasesInSwitch": true, 10 | "noImplicitAny": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noUnusedLocals": true, 14 | "strictPropertyInitialization": false, 15 | "noUnusedParameters": true, 16 | "removeComments": true, 17 | "sourceMap": true, 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "strictNullChecks": true, 21 | "target": "es2017", 22 | "types": ["jest", "node"] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/electron/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/electron/.gitignore: -------------------------------------------------------------------------------- 1 | Electron 2 | !Icon 3 | !lib -------------------------------------------------------------------------------- /packages/electron/.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-links/register' 2 | import '@storybook/addon-backgrounds/register' 3 | import '@storybook/addon-knobs/register' 4 | import '@storybook/addon-actions/register' 5 | -------------------------------------------------------------------------------- /packages/electron/.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure, addParameters, addDecorator } from '@storybook/react' 2 | import { withKnobs } from '@storybook/addon-knobs' 3 | 4 | addParameters({ 5 | backgrounds: [ 6 | { name: 'white', value: 'white', default: true }, 7 | { 8 | name: 'primary', 9 | value: 'gray' 10 | } 11 | ] 12 | }) 13 | 14 | addDecorator(withKnobs) 15 | 16 | // automatically import all files ending in *.stories.tsx 17 | configure(require.context('../src/renderer/', true, /\.stories\.tsx$/), module) 18 | -------------------------------------------------------------------------------- /packages/electron/.storybook/presets.js: -------------------------------------------------------------------------------- 1 | module.exports = ['@storybook/preset-typescript'] 2 | -------------------------------------------------------------------------------- /packages/electron/configs/builder/builder.config.base.ts: -------------------------------------------------------------------------------- 1 | import * as builder from 'electron-builder' 2 | 3 | export const config: builder.Configuration = { 4 | productName: 'Electron', 5 | appId: 'abyssparanoia', 6 | directories: { 7 | output: 'Electron' 8 | }, 9 | files: ['dist/**/*'] 10 | } 11 | -------------------------------------------------------------------------------- /packages/electron/configs/builder/builder.mac.local.ts: -------------------------------------------------------------------------------- 1 | import * as builder from 'electron-builder' 2 | import { config } from './builder.config.base' 3 | 4 | builder 5 | .build({ 6 | config: { 7 | ...config, 8 | mac: { 9 | // Choose your app category. 10 | // see: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/TP40009250-SW8 11 | category: 'public.app-category.developer-tools', 12 | target: ['zip'] 13 | } 14 | } 15 | }) 16 | .catch(e => { 17 | console.error(e) 18 | process.exit(1) 19 | }) 20 | -------------------------------------------------------------------------------- /packages/electron/configs/builder/builder.mac.prod.ts: -------------------------------------------------------------------------------- 1 | import * as builder from 'electron-builder' 2 | import { config } from './builder.config.base' 3 | 4 | builder 5 | .build({ 6 | config: { 7 | ...config, 8 | mac: { 9 | // Choose your app category. 10 | // see: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/TP40009250-SW8 11 | category: 'public.app-category.developer-tools', 12 | target: ['zip'], 13 | publish: { 14 | provider: 'github', 15 | private: true, 16 | owner: 'abyssparanoia', 17 | repo: 'nextjs-firebase-boilerplate', 18 | token: process.env.GH_TOKEN, 19 | releaseType: 'release' 20 | } 21 | } 22 | }, 23 | publish: process.env.DEPLOY_ELECTRON ? 'always' : 'never' 24 | }) 25 | .catch(e => { 26 | console.error(e) 27 | process.exit(1) 28 | }) 29 | -------------------------------------------------------------------------------- /packages/electron/configs/builder/builder.win.prod.ts: -------------------------------------------------------------------------------- 1 | import * as builder from 'electron-builder' 2 | import { config } from './builder.config.base' 3 | 4 | builder 5 | .build({ 6 | config: { 7 | ...config, 8 | win: { 9 | target: { 10 | target: 'nsis', 11 | arch: ['x64', 'ia32'] 12 | }, 13 | publish: { 14 | provider: 'github', 15 | private: true, 16 | owner: 'abyssparanoia', 17 | repo: 'nextjs-firebase-boilerplate', 18 | token: process.env.GH_TOKEN, 19 | releaseType: 'release' 20 | } 21 | } 22 | }, 23 | publish: process.env.DEPLOY_ELECTRON ? 'always' : 'never' 24 | }) 25 | .catch(e => { 26 | console.error(e) 27 | process.exit(1) 28 | }) 29 | -------------------------------------------------------------------------------- /packages/electron/configs/webpack/constants.babel.ts: -------------------------------------------------------------------------------- 1 | export const DEV_SERVER_PORT = 3004 2 | export const publicPath = `http://localhost:${DEV_SERVER_PORT}/dist` 3 | 4 | export const API_ENDPOINT_DEV = 'http://localhost:3001' 5 | export const API_ENDPOINT_STG = '' 6 | export const API_ENDPOINT_PROD = '' 7 | -------------------------------------------------------------------------------- /packages/electron/configs/webpack/webpack.config.dev.babel.ts: -------------------------------------------------------------------------------- 1 | import merge from 'webpack-merge' 2 | import { main, renderer } from './webpack.config.base' 3 | import { API_ENDPOINT_DEV, publicPath } from './constants.babel' 4 | import webpack from 'webpack' 5 | 6 | const devMain = merge( 7 | { 8 | mode: 'development' 9 | }, 10 | main 11 | ) 12 | 13 | const devRenderer = merge( 14 | { 15 | mode: 'development', 16 | 17 | output: { 18 | publicPath, 19 | filename: 'renderer.dev.js' 20 | }, 21 | plugins: [ 22 | new webpack.EnvironmentPlugin({ 23 | API_ENDPOINT: API_ENDPOINT_DEV 24 | }) 25 | ] 26 | }, 27 | renderer 28 | ) 29 | 30 | export default [devMain, devRenderer] 31 | -------------------------------------------------------------------------------- /packages/electron/configs/webpack/webpack.config.prod.babel.ts: -------------------------------------------------------------------------------- 1 | import merge from 'webpack-merge' 2 | import { main, renderer } from './webpack.config.base' 3 | import webpack from 'webpack' 4 | import { API_ENDPOINT_PROD } from './constants.babel' 5 | 6 | const prodMain = merge( 7 | { 8 | mode: 'production' 9 | }, 10 | main 11 | ) 12 | 13 | const prodRenderer = merge( 14 | { 15 | mode: 'production', 16 | plugins: [ 17 | new webpack.EnvironmentPlugin({ 18 | API_ENDPOINT: API_ENDPOINT_PROD 19 | }) 20 | ] 21 | }, 22 | renderer 23 | ) 24 | 25 | export default [prodMain, prodRenderer] 26 | -------------------------------------------------------------------------------- /packages/electron/configs/webpack/webpack.config.server.babel.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from 'child_process' 2 | import webpack from 'webpack' 3 | import merge from 'webpack-merge' 4 | import { renderer } from './webpack.config.base' 5 | import { DEV_SERVER_PORT, publicPath } from './constants.babel' 6 | 7 | export default merge( 8 | { 9 | mode: 'development', 10 | entry: [ 11 | 'react-hot-loader/patch', 12 | `webpack-dev-server/client?http://localhost:${DEV_SERVER_PORT}`, 13 | 'webpack/hot/only-dev-server' 14 | ], 15 | 16 | output: { 17 | publicPath 18 | }, 19 | 20 | plugins: [ 21 | new webpack.NamedModulesPlugin(), 22 | new webpack.HotModuleReplacementPlugin(), 23 | new webpack.NoEmitOnErrorsPlugin() 24 | ], 25 | 26 | devtool: 'inline-source-map', 27 | 28 | devServer: { 29 | port: DEV_SERVER_PORT, 30 | publicPath, 31 | historyApiFallback: true, 32 | hot: true, 33 | after() { 34 | // eslint-disable-next-line no-console 35 | console.log('Starting Main Process...') 36 | spawn('electron', ['./dist/main.js'], { 37 | shell: true, 38 | env: process.env, 39 | stdio: 'inherit' 40 | }) 41 | .on('close', code => process.exit(code)) 42 | .on('error', spawnError => console.error(spawnError)) 43 | } 44 | } 45 | }, 46 | renderer 47 | ) 48 | -------------------------------------------------------------------------------- /packages/electron/configs/webpack/webpack.config.stg.babel.ts: -------------------------------------------------------------------------------- 1 | import merge from 'webpack-merge' 2 | import { main, renderer } from './webpack.config.base' 3 | import webpack from 'webpack' 4 | import { API_ENDPOINT_STG } from './constants.babel' 5 | 6 | const stgMain = merge( 7 | { 8 | mode: 'production' 9 | }, 10 | main 11 | ) 12 | 13 | const stgRenderer = merge( 14 | { 15 | mode: 'production', 16 | plugins: [ 17 | new webpack.EnvironmentPlugin({ 18 | API_ENDPOINT: API_ENDPOINT_STG, 19 | STAGING: true 20 | }) 21 | ] 22 | }, 23 | renderer 24 | ) 25 | 26 | export default [stgMain, stgRenderer] 27 | -------------------------------------------------------------------------------- /packages/electron/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'jsdom', 4 | clearMocks: true, 5 | moduleNameMapper: { 6 | '^src/(.+)': '/src/$1', 7 | '^@renderer/(.+)': '/src/renderer/$1', 8 | '\\.(css|less)$': 'identity-obj-proxy' 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/components/atoms/TextForm/TextForm.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render, fireEvent, screen } from '@testing-library/react' 3 | import { TextForm } from './TextForm' 4 | 5 | describe(`${TextForm.name}`, () => { 6 | test('Snapshot', () => { 7 | const component = render() 8 | const tree = component.baseElement 9 | expect(tree).toMatchSnapshot() 10 | }) 11 | 12 | test('The onChange fires when the Change Event fires.', () => { 13 | const onChange = jest.fn() 14 | const VALUE = 'value' 15 | render() 16 | fireEvent.change(screen.getByDisplayValue(VALUE), { 17 | target: { value: 'chuck' } 18 | }) 19 | expect(onChange).toHaveBeenCalled() 20 | }) 21 | 22 | test('The placeholder is working properly.', () => { 23 | const PLACEHOLDER = 'placeholder' 24 | render() 25 | expect(screen.getByDisplayValue(PLACEHOLDER)) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/components/atoms/TextForm/TextForm.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { storiesOf } from '@storybook/react' 3 | import { TextForm } from './TextForm' 4 | 5 | storiesOf('atoms/TextForm', module).add('Required', () => ) 6 | 7 | storiesOf('atoms/TextForm', module).add('Disabled', () => ) 8 | 9 | storiesOf('atoms/TextForm', module).add('Password', () => ( 10 | 11 | )) 12 | 13 | storiesOf('atoms/TextForm', module).add('number', () => ) 14 | 15 | storiesOf('atoms/TextForm', module).add('Search Field', () => ) 16 | 17 | storiesOf('atoms/TextForm', module).add('Read Only', () => ( 18 | 19 | )) 20 | 21 | storiesOf('atoms/TextForm', module).add('Error', () => ) 22 | 23 | storiesOf('atoms/TextForm', module).add('With Icon', () => ( 24 | 25 | )) 26 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/components/atoms/TextForm/TextForm.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { TextField } from '@material-ui/core' 3 | import InputAdornment from '@material-ui/core/InputAdornment' 4 | import { Icon, Variant as IconVariant } from '../../nano/Icon' 5 | 6 | export interface TextFormProps { 7 | value?: string | number 8 | name?: string 9 | required?: boolean 10 | disabled?: boolean 11 | error?: boolean 12 | readOnly?: boolean 13 | label?: string 14 | type?: 'password' | 'search' | 'number' 15 | defaultValue?: string | number 16 | autoComplete?: string 17 | helperText?: string 18 | placeholder?: string 19 | iconVariant?: IconVariant 20 | iconCursor?: 'pointer' 21 | fullWidth?: boolean 22 | onChange?: (event: React.ChangeEvent) => void 23 | onClickIcon?: () => void 24 | } 25 | 26 | export const TextForm: React.FC = ({ 27 | value, 28 | name, 29 | required, 30 | disabled, 31 | error, 32 | readOnly, 33 | label, 34 | type, 35 | defaultValue, 36 | autoComplete, 37 | helperText, 38 | iconVariant, 39 | iconCursor, 40 | placeholder, 41 | fullWidth, 42 | onChange, 43 | onClickIcon 44 | }) => { 45 | return ( 46 | 64 | 65 | 66 | ) 67 | }} 68 | /> 69 | ) 70 | } 71 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/components/atoms/TextForm/__snapshots__/TextForm.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Snapshot 1`] = ` 4 | 5 |
6 |
9 |
12 | 18 |
19 |
20 |
21 | 22 | `; 23 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/components/atoms/TextForm/index.ts: -------------------------------------------------------------------------------- 1 | export { TextForm } from './TextForm' 2 | export * from './TextForm' 3 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/components/atoms/index.ts: -------------------------------------------------------------------------------- 1 | export { TextForm, TextFormProps } from './TextForm' 2 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/components/nano/Icon/Icon.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { number, select } from '@storybook/addon-knobs' 3 | import { storiesOf } from '@storybook/react' 4 | import { Variant, Icon, Variants } from './Icon' 5 | 6 | storiesOf('nano/Icon', module) 7 | .add('Playground', () => ( 8 | 9 | )) 10 | .add('All Icons', () => { 11 | return ( 12 | <> 13 | {Variants.map((v: Variant) => ( 14 | 15 | 16 | 17 | ))} 18 | 19 | ) 20 | }) 21 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/components/nano/Icon/Icon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import * as BaseSvg from './SvgComponents' 3 | 4 | export const Variants = ['sample'] as const 5 | 6 | export type Variant = typeof Variants[number] 7 | 8 | export interface IconProps { 9 | variant: Variant 10 | width?: number 11 | height?: number 12 | color?: string 13 | } 14 | 15 | const Svg = ({ variant, width, height, color }: IconProps) => { 16 | switch (variant) { 17 | case 'sample': 18 | return 19 | default: 20 | throw new Error('Invalid Icon variant.') 21 | } 22 | } 23 | 24 | export const Icon = ({ variant, width, height, color }: IconProps) => { 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/components/nano/Icon/SvgComponents/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as Sample } from './Sample' 2 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/components/nano/Icon/index.ts: -------------------------------------------------------------------------------- 1 | export { Icon } from './Icon' 2 | export * from './Icon' 3 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/components/nano/Icon/template.js: -------------------------------------------------------------------------------- 1 | function template({ template }, opts, { imports, componentName, props, jsx, exports }) { 2 | const typeScriptTpl = template.smart({ plugins: ['typescript'] }) 3 | return typeScriptTpl.ast` 4 | import React from 'react'; 5 | const ${componentName} = (props: React.SVGProps) => ${jsx}; 6 | export default ${componentName}; 7 | ` 8 | } 9 | module.exports = template 10 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/components/nano/Icon/tsx/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as Sample } from './Sample' 2 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/components/nano/index.ts: -------------------------------------------------------------------------------- 1 | export { Icon, IconProps } from './Icon' 2 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/components/organisms/LoginForm/index.ts: -------------------------------------------------------------------------------- 1 | export { LoginForm } from './LoginForm' 2 | export * from './LoginForm' 3 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/components/organisms/Sample/Sample.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { routerOperations } from '@renderer/states/modules/router' 4 | 5 | export const Sample = () => { 6 | const dispatch = useDispatch() 7 | return
dispatch(routerOperations.pushLogin())}>Link To Login
8 | } 9 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/components/organisms/Sample/index.ts: -------------------------------------------------------------------------------- 1 | export { Sample } from './Sample' 2 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/components/organisms/index.ts: -------------------------------------------------------------------------------- 1 | export { LoginForm } from './LoginForm' 2 | export { Sample } from './Sample' 3 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Electron 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/index.tsx: -------------------------------------------------------------------------------- 1 | import { ConnectedRouter } from 'connected-react-router' 2 | import { Route, Switch } from 'react-router' 3 | import { history } from './states/modules/router' 4 | 5 | import React from 'react' 6 | import ReactDOM from 'react-dom' 7 | import { Provider } from 'react-redux' 8 | import createStore from './states' 9 | 10 | import { LoginForm, Sample } from './components/organisms' 11 | 12 | const store = createStore() 13 | 14 | ReactDOM.render( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | , 23 | document.getElementById('root') as HTMLElement 24 | ) 25 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/lib/http/api/index.ts: -------------------------------------------------------------------------------- 1 | import { postSignIn } from './postSignIn' 2 | 3 | export const api = { 4 | postSignIn 5 | } 6 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/lib/http/api/postSignIn.spec.ts: -------------------------------------------------------------------------------- 1 | import { httpClient } from '../client' 2 | import { postSignIn } from './postSignIn' 3 | import { SignInRequest, SignInResponse } from '@abyssparanoia/interface' 4 | 5 | jest.mock('../client') 6 | 7 | describe(`${postSignIn.name}`, () => { 8 | const postSignInRequest: SignInRequest = { 9 | body: { 10 | userID: 'user-id', 11 | password: 'password' 12 | } 13 | } 14 | test('success', async () => { 15 | const expected: SignInResponse = { 16 | user: { 17 | id: 'id', 18 | role: 'admin', 19 | disabled: false, 20 | createdAt: 123, 21 | updatedAt: 456 22 | }, 23 | customToken: 'custom-token' 24 | } 25 | ;(httpClient as jest.Mock).mockResolvedValue({ json: () => expected }) 26 | const result = await postSignIn(postSignInRequest) 27 | expect(result).toEqual(expected) 28 | }) 29 | 30 | test('failure', async () => { 31 | const error = 'error' 32 | ;(httpClient as jest.Mock).mockRejectedValue(error) 33 | try { 34 | await postSignIn(postSignInRequest) 35 | } catch (error) { 36 | expect(error).toEqual(error) 37 | } 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/lib/http/api/postSignIn.ts: -------------------------------------------------------------------------------- 1 | import { SignInRequest, SignInResponse } from '@abyssparanoia/interface' 2 | import { httpClient } from '../client' 3 | 4 | export const postSignIn = async (postSignIn: SignInRequest): Promise => { 5 | const res = await httpClient('get', '/sign_in', postSignIn.body, { 6 | authorization: true 7 | }).catch(err => { 8 | throw err 9 | }) 10 | return await res.json() 11 | } 12 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/lib/http/client/__tests__/get.test.ts: -------------------------------------------------------------------------------- 1 | import queryString from 'query-string' 2 | import { helpers } from '../get' 3 | 4 | describe('get', () => { 5 | describe('removeNull', () => { 6 | test('success', async () => { 7 | const expected = [['param', 'param']] 8 | const queryOrBody = { param: 'param' } 9 | const result = helpers.removeNull(queryOrBody) 10 | expect(result).toEqual(expected) 11 | }) 12 | }) 13 | 14 | describe('toObject', () => { 15 | test('success', async () => { 16 | const expected = { param: 'param' } 17 | const array: [string, unknown][] = [['param', 'param']] 18 | const result = helpers.toObject(array) 19 | expect(result).toEqual(expected) 20 | }) 21 | }) 22 | 23 | describe('getQueryString', () => { 24 | test('success', async () => { 25 | const queryObj = { param: 'param' } 26 | const spy = jest.spyOn(queryString, 'stringify') 27 | helpers.getQueryString(queryObj) 28 | expect(spy).toHaveBeenCalled() 29 | }) 30 | 31 | test('queryObj is blank', async () => { 32 | const queryObj = {} 33 | const expected = '' 34 | const result = helpers.getQueryString(queryObj) 35 | expect(result).toEqual(expected) 36 | }) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/lib/http/client/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { httpClient } from '../' 2 | 3 | describe('clinet/index', () => { 4 | window.fetch = jest.fn().mockResolvedValue({ 5 | ok: true 6 | }) as any 7 | describe('get', () => { 8 | test('success', async () => { 9 | const spy = jest.spyOn(window, 'fetch') 10 | const queryOrBody = { param: 'param' } 11 | await httpClient('get', '/path', queryOrBody) 12 | expect(spy).toHaveBeenCalled() 13 | }) 14 | }) 15 | 16 | describe('post', () => { 17 | test('success', async () => { 18 | const spy = jest.spyOn(window, 'fetch') 19 | const queryOrBody = { param: 'param' } 20 | await httpClient('post', '/path', queryOrBody) 21 | expect(spy).toHaveBeenCalled() 22 | }) 23 | }) 24 | 25 | describe('put', () => { 26 | test('success', async () => { 27 | const spy = jest.spyOn(window, 'fetch') 28 | const queryOrBody = { param: 'param' } 29 | await httpClient('put', '/path', queryOrBody) 30 | expect(spy).toHaveBeenCalled() 31 | }) 32 | }) 33 | 34 | describe('patch', () => { 35 | test('success', async () => { 36 | const spy = jest.spyOn(window, 'fetch') 37 | const queryOrBody = { param: 'param' } 38 | await httpClient('patch', '/path', queryOrBody) 39 | expect(spy).toHaveBeenCalled() 40 | }) 41 | }) 42 | 43 | describe('delete', () => { 44 | test('success', async () => { 45 | const spy = jest.spyOn(window, 'fetch') 46 | const queryOrBody = { param: 'param' } 47 | await httpClient('delete', '/path', queryOrBody) 48 | expect(spy).toHaveBeenCalled() 49 | }) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/lib/http/client/get.ts: -------------------------------------------------------------------------------- 1 | import { stringify } from 'query-string' 2 | 3 | const removeNull = (queryOrBody: object | FormData) => { 4 | const validParams: [string, unknown][] = Object.entries(queryOrBody).filter( 5 | kvpair => !Object.is(kvpair[1], null) && !Object.is(kvpair[1], undefined) 6 | ) 7 | return validParams 8 | } 9 | 10 | const toObject = (validParams: [string, unknown][]) => { 11 | const queryObj: Record = validParams.reduce((acc: Record, val) => { 12 | acc[val[0]] = val[1] 13 | return acc 14 | }, {}) 15 | return queryObj 16 | } 17 | 18 | const getQueryString = (queryObj: Record) => { 19 | if (Object.keys(queryObj).length > 0) { 20 | return `?${stringify(queryObj)}` 21 | } 22 | return '' 23 | } 24 | 25 | export const get = (queryOrBody: object | FormData) => { 26 | const validParams = removeNull(queryOrBody) 27 | const queryObj = toObject(validParams) 28 | const queryParams = getQueryString(queryObj) 29 | 30 | return { queryOrBody, queryParams } 31 | } 32 | 33 | export const helpers = { 34 | removeNull, 35 | toObject, 36 | getQueryString 37 | } 38 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/lib/http/index.ts: -------------------------------------------------------------------------------- 1 | export { api } from './api' 2 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './store' 2 | export * from './interfaces' 3 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { ActionCreator, Store as BaseStore } from 'redux' 2 | import { ThunkAction as ReduxThunkAction, ThunkDispatch } from 'redux-thunk' 3 | 4 | import * as router from './modules/router' 5 | import * as auth from './modules/auth' 6 | import * as global from './modules/global' 7 | 8 | export interface RootState { 9 | router: router.State 10 | auth: auth.State 11 | global: global.State 12 | } 13 | 14 | export type RootAction = auth.Actions | router.Actions | global.Actions 15 | 16 | type ReduxStore = BaseStore 17 | 18 | export interface Store extends ReduxStore { 19 | dispatch: ThunkDispatch 20 | } 21 | 22 | export type ThunkAction = ReduxThunkAction | void, RootState, undefined, RootAction> 23 | 24 | export type Operation = ActionCreator 25 | 26 | type ValueOf = T[keyof T] 27 | type ReturnTypes = { 28 | [K in keyof T]: T[K] extends (...args: any[]) => any ? ReturnType : never 29 | } 30 | export type ActionsUnion = ValueOf> 31 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/auth/__tests__/actions.spec.ts: -------------------------------------------------------------------------------- 1 | import { actions } from '../actions' 2 | import { ActionTypes } from '../actionTypes' 3 | 4 | describe('user/actions', () => { 5 | test(`${actions.requestPostSignIn.name}`, () => { 6 | const result = actions.requestPostSignIn() 7 | const expected = { 8 | type: ActionTypes.REQUEST_POST_SIGN_IN, 9 | payload: {} 10 | } 11 | expect(result).toEqual(expected) 12 | }) 13 | test(`${actions.successPostSignIn.name}`, () => { 14 | const result = actions.successPostSignIn() 15 | const expected = { 16 | type: ActionTypes.SUCCESS_POST_SIGN_IN, 17 | payload: {} 18 | } 19 | expect(result).toEqual(expected) 20 | }) 21 | test(`${actions.failurePostSignIn.name}`, () => { 22 | const result = actions.failurePostSignIn() 23 | const expected = { 24 | type: ActionTypes.FAILURE_POST_SIGN_IN, 25 | payload: {} 26 | } 27 | expect(result).toEqual(expected) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/auth/__tests__/operations/signIn.spec.ts: -------------------------------------------------------------------------------- 1 | import configureMockStore from 'redux-mock-store' 2 | import thunk from 'redux-thunk' 3 | import { api } from '../../../../../lib/http' 4 | import { authOperations } from '../..' 5 | import { AuthActionTypes } from '../..' 6 | 7 | const middlewares = [thunk] 8 | const mockStore = configureMockStore(middlewares) 9 | jest.mock('../../../../../lib/http') 10 | 11 | describe(`${authOperations.signIn.name}`, () => { 12 | beforeAll(() => {}) 13 | test('success', async () => { 14 | const store = mockStore() 15 | api.postSignIn = jest.fn().mockResolvedValue({ id: 1 }) 16 | const expectedActions = [ 17 | { 18 | type: AuthActionTypes.REQUEST_POST_SIGN_IN, 19 | payload: {} 20 | }, 21 | { 22 | type: AuthActionTypes.SUCCESS_POST_SIGN_IN, 23 | payload: {} 24 | } 25 | ] 26 | await store.dispatch(authOperations.signIn() as any) 27 | expect(store.getActions()).toEqual(expectedActions) 28 | }) 29 | test('failure', async () => { 30 | const store = mockStore() 31 | api.postSignIn = jest.fn().mockRejectedValue({}) 32 | const expectedActions = [ 33 | { 34 | type: AuthActionTypes.REQUEST_POST_SIGN_IN, 35 | payload: {} 36 | }, 37 | { 38 | type: AuthActionTypes.FAILURE_POST_SIGN_IN, 39 | payload: {} 40 | } 41 | ] 42 | await store.dispatch(authOperations.signIn() as any) 43 | expect(store.getActions()).toEqual(expectedActions) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/auth/__tests__/reducers.test.ts: -------------------------------------------------------------------------------- 1 | import { deepCopy } from '../../../test-utils' 2 | import { initialState, reducer } from '../reducers' 3 | import { ActionTypes } from '../actionTypes' 4 | 5 | describe('user/reducer', () => { 6 | test('initialState', () => { 7 | const state = undefined 8 | const action = {} 9 | const result = reducer(state, action as any) 10 | const expected = { ...initialState } 11 | expect(result).toEqual(expected) 12 | }) 13 | 14 | test(`${ActionTypes.REQUEST_POST_SIGN_IN}`, () => { 15 | const state = deepCopy(initialState) 16 | const action = { 17 | type: ActionTypes.REQUEST_POST_SIGN_IN, 18 | payload: {} 19 | } 20 | const result = reducer(state, action) 21 | const expected = { ...state, isLoading: true } 22 | expect(result).toEqual(expected) 23 | }) 24 | test(`${ActionTypes.SUCCESS_POST_SIGN_IN}`, () => { 25 | const state = deepCopy(initialState) 26 | const action = { 27 | type: ActionTypes.SUCCESS_POST_SIGN_IN, 28 | payload: {} 29 | } 30 | const result = reducer(state, action) 31 | const expected = { ...state, isLoading: false } 32 | expect(result).toEqual(expected) 33 | }) 34 | test(`${ActionTypes.FAILURE_POST_SIGN_IN}`, () => { 35 | const state = deepCopy(initialState) 36 | const action = { 37 | type: ActionTypes.FAILURE_POST_SIGN_IN, 38 | payload: {} 39 | } 40 | const result = reducer(state, action) 41 | const expected = { ...state, isLoading: false } 42 | expect(result).toEqual(expected) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/auth/__tests__/selectors.spec.ts: -------------------------------------------------------------------------------- 1 | import { deepCopy } from '../../../test-utils' 2 | import { RootState } from '../../..' 3 | import createStore from '../../../store' 4 | import { selectors } from '../selectors' 5 | 6 | const baseState: RootState = createStore().getState() 7 | 8 | describe('auth/selectors', () => { 9 | test(`${selectors.getIsLoading.name}`, () => { 10 | const state: RootState = deepCopy(baseState) 11 | const expected = true 12 | state.auth.isLoading = expected 13 | const result = selectors.getIsLoading(state) 14 | expect(result).toEqual(expected) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/auth/actionTypes.ts: -------------------------------------------------------------------------------- 1 | export const ActionTypes = { 2 | REQUEST_POST_SIGN_IN: '@user/REQUEST_POST_SIGN_IN', 3 | SUCCESS_POST_SIGN_IN: '@user/SUCCESS_POST_SIGN_IN', 4 | FAILURE_POST_SIGN_IN: '@user/FAILURE_POST_SIGN_IN' 5 | } as const 6 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/auth/actions.ts: -------------------------------------------------------------------------------- 1 | import { ActionTypes } from './actionTypes' 2 | 3 | export const actions = { 4 | requestPostSignIn: () => ({ 5 | type: ActionTypes.REQUEST_POST_SIGN_IN, 6 | payload: {} 7 | }), 8 | successPostSignIn: () => ({ 9 | type: ActionTypes.SUCCESS_POST_SIGN_IN, 10 | payload: {} 11 | }), 12 | failurePostSignIn: () => ({ 13 | type: ActionTypes.FAILURE_POST_SIGN_IN, 14 | payload: {} 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/auth/factories.ts: -------------------------------------------------------------------------------- 1 | import { faker, toArray } from '../../test-utils' 2 | import { User } from '@abyssparanoia/interface' 3 | 4 | const user = (): User => ({ 5 | id: faker.random.uuid(), 6 | role: 'admin', 7 | disabled: faker.random.boolean(), 8 | createdAt: faker.random.number(), 9 | updatedAt: faker.random.number() 10 | }) 11 | 12 | export const factories = { 13 | userList: toArray(user, 30, 30) 14 | } 15 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/auth/index.ts: -------------------------------------------------------------------------------- 1 | import { actions } from './actions' 2 | import { operations } from './operations' 3 | import { Actions, initialState, reducer, State } from './reducers' 4 | import { selectors } from './selectors' 5 | import { ActionTypes } from './actionTypes' 6 | 7 | export { 8 | Actions, 9 | initialState, 10 | State, 11 | ActionTypes as AuthActionTypes, 12 | actions as authActions, 13 | operations as authOperations, 14 | selectors as authSelectors 15 | } 16 | export default reducer 17 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/auth/operations.ts: -------------------------------------------------------------------------------- 1 | import { Operation } from '@renderer/states/interfaces' 2 | import { api } from '@renderer/lib/http' 3 | import { authActions } from '.' 4 | 5 | const signIn: Operation = (userID: string, password: string) => async dispatch => { 6 | dispatch(authActions.requestPostSignIn()) 7 | await api 8 | .postSignIn({ body: { userID, password } }) 9 | .then(() => { 10 | dispatch(authActions.successPostSignIn()) 11 | }) 12 | .catch(() => { 13 | dispatch(authActions.failurePostSignIn()) 14 | }) 15 | } 16 | 17 | export const operations = { 18 | signIn 19 | } 20 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/auth/reducers.ts: -------------------------------------------------------------------------------- 1 | import { Reducer } from 'redux' 2 | import { ActionsUnion } from '@renderer/states/interfaces' 3 | import { actions } from './actions' 4 | import { ActionTypes } from './actionTypes' 5 | import { User } from '@abyssparanoia/interface' 6 | 7 | export type Actions = ActionsUnion 8 | 9 | export interface State { 10 | user?: User 11 | isLoading: boolean 12 | } 13 | 14 | export const initialState: State = { 15 | isLoading: false 16 | } 17 | 18 | export const reducer: Reducer = (state = initialState, action): State => { 19 | switch (action.type) { 20 | case ActionTypes.REQUEST_POST_SIGN_IN: 21 | return { ...state, isLoading: true } 22 | case ActionTypes.SUCCESS_POST_SIGN_IN: 23 | return { ...state, isLoading: false } 24 | case ActionTypes.FAILURE_POST_SIGN_IN: 25 | return { ...state, isLoading: false } 26 | default: 27 | return { ...state } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/auth/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | import { RootState } from '@renderer/states/interfaces' 3 | import { State } from './reducers' 4 | import { User } from '@abyssparanoia/interface' 5 | 6 | const authSelector = (state: RootState): State => state.auth 7 | 8 | const getUser = createSelector(authSelector, (state: State): User | undefined => state.user) 9 | const getIsLoading = createSelector(authSelector, (state: State): boolean => state.isLoading) 10 | 11 | export const selectors = { 12 | getUser, 13 | getIsLoading 14 | } 15 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/global/__tests__/actions.test.ts: -------------------------------------------------------------------------------- 1 | import { actions } from '../actions' 2 | import { ActionTypes } from '../actionTypes' 3 | import { factories } from '../factory' 4 | 5 | describe('global/actions', () => { 6 | test(`${actions.addErrorMessage.name}`, () => { 7 | const errorMessage = factories.errorMessageList[0] 8 | const result = actions.addErrorMessage(errorMessage) 9 | const expected = { 10 | type: ActionTypes.ADD_ERROR_MESSAGE, 11 | payload: { errorMessage } 12 | } 13 | expect(result).toEqual(expected) 14 | }) 15 | 16 | test(`${actions.popErrorMessage.name}`, () => { 17 | const result = actions.popErrorMessage() 18 | const expected = { 19 | type: ActionTypes.POP_ERROR_MESSAGE, 20 | payload: {} 21 | } 22 | expect(result).toEqual(expected) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/global/__tests__/reducers.test.ts: -------------------------------------------------------------------------------- 1 | import { initialState, reducer } from '../reducers' 2 | import { ActionTypes } from '../actionTypes' 3 | import { factories } from '../factory' 4 | 5 | describe('global/reducer', () => { 6 | test('initialState', () => { 7 | const state = undefined 8 | const action = {} 9 | const result = reducer(state, action as any) 10 | const expected = initialState 11 | expect(result).toEqual(expected) 12 | }) 13 | 14 | test(`${ActionTypes.ADD_ERROR_MESSAGE}`, () => { 15 | const state = { ...initialState } 16 | const errorMessage = factories.errorMessageList[0] 17 | const action = { 18 | type: ActionTypes.ADD_ERROR_MESSAGE, 19 | payload: { errorMessage } 20 | } 21 | const result = reducer(state, action) 22 | const expected = { ...state, errorMessageList: [errorMessage] } 23 | expect(result).toEqual(expected) 24 | }) 25 | 26 | test(`${ActionTypes.POP_ERROR_MESSAGE}`, () => { 27 | const errorMessageList = factories.errorMessageList 28 | const state = { ...initialState, errorMessageList } 29 | const action = { 30 | type: ActionTypes.POP_ERROR_MESSAGE, 31 | payload: {} 32 | } 33 | const result = reducer(state, action) 34 | 35 | errorMessageList.pop() 36 | const expected = { ...state, errorMessageList } 37 | expect(result).toEqual(expected) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/global/__tests__/selectors.test.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from '../../../index' 2 | import createStore from '../../../store' 3 | import { selectors } from '../selectors' 4 | import { factories } from '../factory' 5 | 6 | const baseState: RootState = createStore().getState() 7 | 8 | describe('global/selectors', () => { 9 | test(`${selectors.getErrorMessageList.name}`, () => { 10 | const state: RootState = { ...baseState } 11 | const expected = factories.errorMessageList 12 | state.global.errorMessageList = expected 13 | const result = selectors.getErrorMessageList(state) 14 | expect(result).toEqual(expected) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/global/actionTypes.ts: -------------------------------------------------------------------------------- 1 | export const ActionTypes = { 2 | ADD_ERROR_MESSAGE: '@global/ADD_ERROR_MESSAGE', 3 | POP_ERROR_MESSAGE: '@global/POP_ERROR_MESSAGE', 4 | RESET_ALL: '@global/RESET_ALL' 5 | } as const 6 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/global/actions.ts: -------------------------------------------------------------------------------- 1 | import { ActionTypes } from './actionTypes' 2 | 3 | export const actions = { 4 | addErrorMessage: (errorMessage: string) => ({ 5 | type: ActionTypes.ADD_ERROR_MESSAGE, 6 | payload: { errorMessage } 7 | }), 8 | 9 | popErrorMessage: () => ({ 10 | type: ActionTypes.POP_ERROR_MESSAGE, 11 | payload: {} 12 | }), 13 | 14 | resetAll: () => ({ 15 | type: ActionTypes.RESET_ALL, 16 | payload: {} 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/global/factory.ts: -------------------------------------------------------------------------------- 1 | import { faker, toArray } from '../../test-utils' 2 | 3 | const errorMessage = (): string => faker.random.words() 4 | 5 | export const factories = { 6 | errorMessageList: toArray(errorMessage) 7 | } 8 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/global/index.ts: -------------------------------------------------------------------------------- 1 | import { actions } from './actions' 2 | import { operations } from './operations' 3 | import { Actions, initialState, reducer, State } from './reducers' 4 | import { selectors } from './selectors' 5 | import { ActionTypes } from './actionTypes' 6 | 7 | export { 8 | Actions, 9 | initialState, 10 | State, 11 | ActionTypes as GlobalActionTypes, 12 | actions as globalActions, 13 | operations as globalOperations, 14 | selectors as globalSelectors 15 | } 16 | export default reducer 17 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/global/operations.ts: -------------------------------------------------------------------------------- 1 | import { globalActions } from './' 2 | 3 | export const operations = { 4 | addErrorMessage: globalActions.addErrorMessage, 5 | popErrorMessage: globalActions.popErrorMessage, 6 | resetAll: globalActions.resetAll 7 | } 8 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/global/reducers.ts: -------------------------------------------------------------------------------- 1 | import { Reducer } from 'redux' 2 | import { ActionsUnion } from '@renderer/states' 3 | import { actions } from './actions' 4 | import { ActionTypes } from './actionTypes' 5 | 6 | export type Actions = ActionsUnion 7 | 8 | export interface State { 9 | errorMessageList: string[] 10 | } 11 | 12 | export const initialState: State = { 13 | errorMessageList: [] 14 | } 15 | 16 | export const reducer: Reducer = (state = initialState, action): State => { 17 | switch (action.type) { 18 | case ActionTypes.ADD_ERROR_MESSAGE: 19 | return { ...state, errorMessageList: [...state.errorMessageList, action.payload.errorMessage] } 20 | 21 | case ActionTypes.POP_ERROR_MESSAGE: 22 | state.errorMessageList.pop() 23 | return { ...state, errorMessageList: state.errorMessageList } 24 | 25 | default: 26 | return { ...state } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/global/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | import { RootState } from '@renderer/states' 3 | import { State } from './reducers' 4 | 5 | const globalSelector = (state: RootState): State => state.global 6 | 7 | const getErrorMessageList = createSelector(globalSelector, (state: State): string[] => state.errorMessageList) 8 | 9 | export const selectors = { 10 | getErrorMessageList 11 | } 12 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/router/actions.ts: -------------------------------------------------------------------------------- 1 | import { push } from 'connected-react-router' 2 | 3 | export const actions = { 4 | pushLogin: () => push('/'), 5 | pushSample: () => push('/sample') 6 | } 7 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/router/index.ts: -------------------------------------------------------------------------------- 1 | import { CALL_HISTORY_METHOD, connectRouter, RouterAction, routerMiddleware, RouterState } from 'connected-react-router' 2 | import { createHashHistory } from 'history' 3 | import { actions } from './actions' 4 | import { operations } from './operations' 5 | import { selectors } from './selectors' 6 | 7 | export const history = createHashHistory() 8 | const reducer = connectRouter(history) 9 | 10 | export const connectedReactRouter = routerMiddleware(history) 11 | 12 | export const methods = { 13 | PUSH: 'push' as 'push' 14 | } 15 | 16 | const ActionTypes = { 17 | CALL_HISTORY_METHOD 18 | } 19 | 20 | export type Actions = RouterAction 21 | export type State = RouterState 22 | 23 | export { 24 | ActionTypes as RouterActionTypes, 25 | actions as routerActions, 26 | operations as routerOperations, 27 | selectors as routerSelectors 28 | } 29 | 30 | export default reducer 31 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/router/operations.ts: -------------------------------------------------------------------------------- 1 | import { Operation } from '../../interfaces' 2 | import { routerActions, routerSelectors } from '.' 3 | 4 | const pushLogin: Operation = () => (dispatch, getState) => { 5 | const state = getState() 6 | const pathName = routerSelectors.getPathname(state) 7 | if (pathName !== '/') { 8 | dispatch(routerActions.pushLogin()) 9 | } 10 | } 11 | 12 | const pushSample: Operation = () => (dispatch, getState) => { 13 | const state = getState() 14 | const pathName = routerSelectors.getPathname(state) 15 | 16 | if (pathName !== '/sample') { 17 | dispatch(routerActions.pushSample()) 18 | } 19 | } 20 | 21 | export const operations = { 22 | pushLogin, 23 | pushSample 24 | } 25 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/modules/router/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | import { RootState } from '@renderer/states/interfaces' 3 | import { State } from '.' 4 | 5 | const routerSelector = (state: RootState): State => state.router 6 | 7 | const getLocation = createSelector(routerSelector, (state: State) => state.location) 8 | 9 | const getPathname = createSelector(getLocation, (state): string => state.pathname) 10 | 11 | export const selectors = { 12 | getPathname 13 | } 14 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/store.ts: -------------------------------------------------------------------------------- 1 | import { applyMiddleware, combineReducers, createStore, AnyAction } from 'redux' 2 | import { composeWithDevTools } from 'redux-devtools-extension' 3 | import thunk, { ThunkMiddleware } from 'redux-thunk' 4 | 5 | import { default as auth } from './modules/auth' 6 | import { connectedReactRouter, default as router } from './modules/router' 7 | import { default as global } from './modules/global' 8 | 9 | import { RootAction, RootState, Store } from './interfaces' 10 | 11 | export default (preloadedState?: RootState): Store => { 12 | const appReducer = combineReducers({ 13 | router, 14 | auth, 15 | global 16 | }) 17 | 18 | const rootReducer: typeof appReducer = (state: RootState | undefined, action: AnyAction): RootState => { 19 | return appReducer(state, action) 20 | } 21 | 22 | const middleware = [connectedReactRouter, thunk as ThunkMiddleware] 23 | const enhancer = composeWithDevTools(applyMiddleware(...middleware)) 24 | 25 | return createStore(rootReducer, preloadedState, enhancer) 26 | } 27 | -------------------------------------------------------------------------------- /packages/electron/src/renderer/states/test-utils.ts: -------------------------------------------------------------------------------- 1 | import faker from 'faker' 2 | 3 | // fix seed 4 | faker.seed(123) 5 | 6 | const times = (n: number, cb: () => T): T[] => { 7 | const result: T[] = [] 8 | for (let index = 0; index < n; index++) { 9 | result.push(cb()) 10 | } 11 | return result 12 | } 13 | 14 | export const toArray = (cb: () => T, min = 1, max = 5): T[] => { 15 | return times(faker.random.number({ min, max }), cb) 16 | } 17 | 18 | export const deepCopy = (base: T): T => JSON.parse(JSON.stringify(base)) 19 | export { faker } 20 | -------------------------------------------------------------------------------- /packages/firebase-admin/.gitignore: -------------------------------------------------------------------------------- 1 | cjs 2 | esm -------------------------------------------------------------------------------- /packages/firebase-admin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@abyssparanoia/firebase-admin", 3 | "version": "0.0.1", 4 | "private": true, 5 | "main": "cjs/index.js", 6 | "module": "esm/index.js", 7 | "typings": "esm/index.d.ts", 8 | "scripts": { 9 | "build": "run-p -l build:*", 10 | "build:cjs": "tsc --project . --module commonjs --outDir ./cjs", 11 | "build:esm": "tsc --project . --module es2015 --outDir ./esm", 12 | "clean": "rimraf esm cjs", 13 | "lint": "tsc -p . --noEmit", 14 | "prebuild": "yarn clean" 15 | }, 16 | "dependencies": { 17 | "firebase-admin": "9.12.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/firebase-admin/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as admin from 'firebase-admin' 2 | 3 | const app = process.env.GCLOUD_PROJECT 4 | ? admin.initializeApp() 5 | : admin.initializeApp({ 6 | credential: admin.credential.applicationDefault() 7 | }) 8 | 9 | const adminAuth = app.auth() 10 | 11 | const FIREBASE_CLIENT_API_KEY = process.env.FIREBASE_CLIENT_API_KEY 12 | 13 | if (!FIREBASE_CLIENT_API_KEY) { 14 | throw new Error(`NO FIREBASE CLIENT API KEY`) 15 | } 16 | 17 | export { app, adminAuth, FIREBASE_CLIENT_API_KEY } 18 | 19 | declare global { 20 | namespace Express { 21 | interface Request { 22 | firebaseAuth: admin.auth.Auth 23 | firebaseStore: admin.firestore.Firestore 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/firebase-admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "target": "es5", 5 | "lib": ["dom", "es2018"], 6 | "strict": true, 7 | "strictNullChecks": true, 8 | "noImplicitAny": true, 9 | "noImplicitReturns": true, 10 | "skipLibCheck": true, 11 | "declaration": true, 12 | "declarationMap": true, 13 | "pretty": true, 14 | "newLine": "lf", 15 | "esModuleInterop": true, 16 | "moduleResolution": "node" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/firebase-client/.gitignore: -------------------------------------------------------------------------------- 1 | cjs 2 | esm -------------------------------------------------------------------------------- /packages/firebase-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@abyssparanoia/firebase-client", 3 | "version": "0.0.1", 4 | "private": true, 5 | "main": "cjs/index.js", 6 | "module": "esm/index.js", 7 | "typings": "esm/index.d.ts", 8 | "scripts": { 9 | "build": "run-p -l build:*", 10 | "build:cjs": "tsc --project . --module commonjs --outDir ./cjs", 11 | "build:esm": "tsc --project . --module es2015 --outDir ./esm", 12 | "clean": "rimraf esm cjs", 13 | "lint": "tsc -p . --noEmit", 14 | "prebuild": "yarn clean" 15 | }, 16 | "dependencies": { 17 | "firebase": "7.24.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/firebase-client/src/index.ts: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase/app' 2 | import 'firebase/auth' 3 | 4 | if (!firebase.apps.length) { 5 | firebase.initializeApp(require('../firebase.client.key.json')) 6 | } 7 | 8 | const auth = firebase.auth() 9 | 10 | class FirebaseAuthenticationError extends Error { 11 | constructor(error: firebase.auth.Error) { 12 | super(error.message) 13 | this.name = new.target.name 14 | Object.setPrototypeOf(this, new.target.prototype) 15 | 16 | // https://github.com/firebase/firebase-js-sdk/blob/master/packages/auth/src/error_auth.js 17 | switch (error.code) { 18 | case 'auth/email-already-in-use': 19 | this.message = '入力されたメールアドレスはすでに使用されています。' 20 | break 21 | case 'auth/invalid-email': 22 | this.message = '不正なメールアドレスです' 23 | break 24 | case 'auth/user-not-found': 25 | this.message = 'ユーザーが見つかりませんでした' 26 | break 27 | case 'auth/wrong-password': 28 | this.message = 'パスワードが一致しません' 29 | break 30 | default: 31 | this.message = 'エラーが発生しました' 32 | break 33 | } 34 | } 35 | } 36 | 37 | export { firebase, auth, FirebaseAuthenticationError } 38 | -------------------------------------------------------------------------------- /packages/firebase-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "target": "es5", 5 | "lib": ["dom", "es2018"], 6 | "strict": true, 7 | "strictNullChecks": true, 8 | "noImplicitAny": true, 9 | "noImplicitReturns": true, 10 | "skipLibCheck": true, 11 | "declaration": true, 12 | "declarationMap": true, 13 | "pretty": true, 14 | "newLine": "lf", 15 | "esModuleInterop": true, 16 | "moduleResolution": "node" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/fixtures/.gitignore: -------------------------------------------------------------------------------- 1 | cjs 2 | esm -------------------------------------------------------------------------------- /packages/fixtures/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@abyssparanoia/fixtures", 3 | "version": "0.0.1", 4 | "private": true, 5 | "main": "cjs/index.js", 6 | "module": "esm/index.js", 7 | "typings": "esm/index.d.ts", 8 | "scripts": { 9 | "build": "run-p -l build:*", 10 | "build:cjs": "tsc --project . --module commonjs --outDir ./cjs", 11 | "build:esm": "tsc --project . --module es2015 --outDir ./esm", 12 | "watch": "nodemon -x yarn build --ext ts --watch src", 13 | "clean": "rimraf esm cjs", 14 | "lint": "tsc -p . --noEmit", 15 | "prebuild": "yarn clean" 16 | }, 17 | "dependencies": { 18 | "@abyssparanoia/firebase-client": "0.0.1", 19 | "@abyssparanoia/firebase-admin": "0.0.1", 20 | "axios": "0.24.0", 21 | "convert-keys": "1.3.4", 22 | "next": "10.2.3", 23 | "nookies": "2.5.2", 24 | "swr": "0.5.7" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/fixtures/src/auth/cookie.ts: -------------------------------------------------------------------------------- 1 | import { NextPageContext } from 'next' 2 | import { parseCookies, setCookie, destroyCookie } from 'nookies' 3 | 4 | type Credential = { 5 | idToken: string 6 | refreshToken: string 7 | } 8 | 9 | export const getTokenFromCookie = (ctx?: NextPageContext): Partial => { 10 | const { idToken, refreshToken } = (parseCookies(ctx) as unknown) as Partial 11 | if (idToken && refreshToken) { 12 | return { idToken, refreshToken } 13 | } 14 | return {} 15 | } 16 | 17 | export const setTokenToCookie = (cred: Credential, ctx?: NextPageContext) => { 18 | setCookie(ctx, 'idToken', cred.idToken, { 19 | maxAge: 30 * 24 * 60 * 60, 20 | path: '/' 21 | }) 22 | setCookie(ctx, 'refreshToken', cred.refreshToken, { 23 | maxAge: 30 * 24 * 60 * 60, 24 | path: '/' 25 | }) 26 | } 27 | 28 | export const removeTokenFromCookie = (ctx?: NextPageContext) => { 29 | destroyCookie(ctx, 'idToken') 30 | destroyCookie(ctx, 'refreshToken') 31 | } 32 | -------------------------------------------------------------------------------- /packages/fixtures/src/auth/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cookie' 2 | export * from './middleware' 3 | export * from './hooks' 4 | -------------------------------------------------------------------------------- /packages/fixtures/src/auth/middleware.ts: -------------------------------------------------------------------------------- 1 | import { ExNextPageContext } from 'next' 2 | import { IncomingMessage } from 'http' 3 | import { stringify } from 'query-string' 4 | import { getTokenFromCookie, setTokenToCookie } from './cookie' 5 | import Router from 'next/router' 6 | import axios from 'axios' 7 | 8 | const apiUrl = (path: string, req?: IncomingMessage) => { 9 | if (req && typeof window === 'undefined') { 10 | // this is running server-side, so we need an absolute URL 11 | const { host } = req.headers 12 | if (host && host.startsWith('localhost')) { 13 | return `http://${host}${path}` 14 | } 15 | return `https://${host}${path}` 16 | } 17 | return path 18 | } 19 | 20 | interface IValidateRequest { 21 | idToken: string 22 | refreshToken: string 23 | } 24 | 25 | interface IValidateResponse { 26 | idToken: string 27 | refreshToken: string 28 | } 29 | 30 | export const authorize = async (ctx: ExNextPageContext) => { 31 | const { req, res } = ctx 32 | 33 | try { 34 | const { idToken, refreshToken } = getTokenFromCookie(ctx) 35 | 36 | // check id token on server 37 | if (req && idToken && refreshToken) { 38 | const param: IValidateRequest = { idToken, refreshToken } 39 | const { data } = await axios.post(apiUrl(`/api/validate`, req), { ...param }) 40 | setTokenToCookie({ idToken: data.idToken, refreshToken: data.refreshToken }) 41 | return 42 | } 43 | 44 | throw new Error(`UNAUTHNETICATED`) 45 | } catch (err) { 46 | if (req && res) { 47 | const redirectTo = req.url 48 | 49 | res!.writeHead(302, { 50 | Location: `/sign_in?${stringify({ redirectTo })}` 51 | }) 52 | res.end() 53 | return 54 | } else { 55 | const redirectTo = Router.pathname 56 | Router.push(`/sign_in?${stringify({ redirectTo })}`) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/fixtures/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth' 2 | export * from './utility' 3 | export * from './swr-cache-path' 4 | -------------------------------------------------------------------------------- /packages/fixtures/src/swr-cache-path.ts: -------------------------------------------------------------------------------- 1 | export const SWRCachePath = { 2 | AUTH_COOKIE: `/auth/cookie` 3 | } as const 4 | -------------------------------------------------------------------------------- /packages/fixtures/src/types/next.d.ts: -------------------------------------------------------------------------------- 1 | import Express from 'express' 2 | import { NextPageContext } from 'next' 3 | import { DocumentContext } from 'next/document' 4 | 5 | declare module 'next' { 6 | type ExNextPageContext = Omit & { 7 | req?: Express.Request 8 | res?: Express.Response 9 | } 10 | } 11 | 12 | declare module 'next/document' { 13 | type ExDocumentContext = DocumentContext & { 14 | req?: Express.Request 15 | res?: Express.Response 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/fixtures/src/utility/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, DependencyList } from 'react' 2 | 3 | export function useEffectAsync(effect: () => void, deps?: DependencyList): void { 4 | useEffect(() => { 5 | effect() 6 | // eslint-disable-next-line react-hooks/exhaustive-deps 7 | }, deps) 8 | } 9 | 10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 11 | export type UnwrapFunc = T extends (...arg: any) => Promise ? U : T 12 | -------------------------------------------------------------------------------- /packages/fixtures/src/utility/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hooks' 2 | export * from './ssr' 3 | -------------------------------------------------------------------------------- /packages/fixtures/src/utility/ssr.ts: -------------------------------------------------------------------------------- 1 | export const isBrowser = () => typeof window !== 'undefined' 2 | -------------------------------------------------------------------------------- /packages/fixtures/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "target": "es5", 5 | "lib": ["dom", "es2018"], 6 | "strict": true, 7 | "strictNullChecks": true, 8 | "noImplicitAny": true, 9 | "noImplicitReturns": true, 10 | "skipLibCheck": true, 11 | "declaration": true, 12 | "declarationMap": true, 13 | "pretty": true, 14 | "newLine": "lf", 15 | "esModuleInterop": true, 16 | "moduleResolution": "node" 17 | }, 18 | "include": ["../fixtures/src/types/*.d.ts", "**/*.ts", "**/*.tsx"] 19 | } 20 | -------------------------------------------------------------------------------- /packages/graphql/.gitignore: -------------------------------------------------------------------------------- 1 | cjs 2 | esm -------------------------------------------------------------------------------- /packages/graphql/codegen.yml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: '../backend-graphql/src/schema.graphql' 3 | documents: ['src/queries/**.graphql', 'src/mutations/**.graphql', 'src/fragments/**.graphql'] 4 | generates: 5 | src/react-apollo/generated.tsx: 6 | plugins: 7 | - 'typescript' 8 | - 'typescript-operations' 9 | - 'typescript-react-apollo' 10 | config: 11 | withComponent: false 12 | withHooks: true 13 | withHOC: false 14 | maybeValue: T 15 | -------------------------------------------------------------------------------- /packages/graphql/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@abyssparanoia/graphql", 3 | "version": "0.0.1", 4 | "private": true, 5 | "main": "cjs/index.js", 6 | "module": "esm/index.js", 7 | "typings": "esm/index.d.ts", 8 | "scripts": { 9 | "build": "graphql-codegen --config codegen.yml && run-p -l build:* && yarn format", 10 | "build:cjs": "tsc --project . --module commonjs --outDir ./cjs", 11 | "build:esm": "tsc --project . --module es2015 --outDir ./esm", 12 | "clean": "rimraf esm cjs", 13 | "lint": "tsc -p . --noEmit", 14 | "format": "prettier --write \"src/**/*.{ts,tsx}\"", 15 | "prebuild": "yarn clean" 16 | }, 17 | "dependencies": { 18 | "@graphql-codegen/cli": "2.3.0", 19 | "@graphql-codegen/typescript": "2.4.0", 20 | "@graphql-codegen/typescript-operations": "2.2.0", 21 | "@graphql-codegen/typescript-react-apollo": "3.2.1", 22 | "react-apollo": "3.1.5" 23 | }, 24 | "devDependencies": { 25 | "@graphql-codegen/cli": "2.3.0", 26 | "@graphql-codegen/introspection": "2.1.0", 27 | "@graphql-codegen/typescript": "2.4.0", 28 | "@graphql-codegen/typescript-operations": "2.2.0", 29 | "@graphql-codegen/typescript-react-apollo": "3.2.1", 30 | "@graphql-codegen/typescript-resolvers": "2.4.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/graphql/src/fragments/user.graphql: -------------------------------------------------------------------------------- 1 | fragment user on User { 2 | id 3 | name 4 | } 5 | -------------------------------------------------------------------------------- /packages/graphql/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './react-apollo/generated' 2 | -------------------------------------------------------------------------------- /packages/graphql/src/mutations/createUser.graphql: -------------------------------------------------------------------------------- 1 | mutation CreateUser($param: UserCreateInput!) { 2 | createUser(param: $param) { 3 | ...user 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/graphql/src/queries/listUsers.graphql: -------------------------------------------------------------------------------- 1 | # Write your query or mutation here 2 | query ListUsers { 3 | list { 4 | ...user 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/graphql/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "target": "es5", 5 | "lib": ["dom", "es2018"], 6 | "strict": true, 7 | "strictNullChecks": true, 8 | "noImplicitAny": true, 9 | "noImplicitReturns": true, 10 | "skipLibCheck": true, 11 | "declaration": true, 12 | "declarationMap": true, 13 | "pretty": true, 14 | "newLine": "lf", 15 | "esModuleInterop": true, 16 | "moduleResolution": "node", 17 | "jsx": "react" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/interface/.gitignore: -------------------------------------------------------------------------------- 1 | cjs 2 | esm -------------------------------------------------------------------------------- /packages/interface/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@abyssparanoia/interface", 3 | "version": "0.0.1", 4 | "private": true, 5 | "main": "cjs/index.js", 6 | "module": "esm/index.js", 7 | "typings": "esm/index.d.ts", 8 | "scripts": { 9 | "build": "run-p -l build:*", 10 | "build:cjs": "tsc --project . --module commonjs --outDir ./cjs", 11 | "build:esm": "tsc --project . --module es2015 --outDir ./esm", 12 | "clean": "rimraf esm cjs", 13 | "lint": "tsc -p . --noEmit", 14 | "prebuild": "yarn clean" 15 | }, 16 | "dependencies": {} 17 | } 18 | -------------------------------------------------------------------------------- /packages/interface/src/entity/claims.ts: -------------------------------------------------------------------------------- 1 | export type Role = 'admin' | 'member' 2 | 3 | export interface Claims { 4 | role: Role 5 | } 6 | -------------------------------------------------------------------------------- /packages/interface/src/entity/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user' 2 | export * from './claims' 3 | -------------------------------------------------------------------------------- /packages/interface/src/entity/user.ts: -------------------------------------------------------------------------------- 1 | import { Role } from './claims' 2 | 3 | export interface User { 4 | id: string 5 | role: Role 6 | disabled: boolean 7 | createdAt: number 8 | updatedAt: number 9 | } 10 | -------------------------------------------------------------------------------- /packages/interface/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './entity' 2 | export * from './request' 3 | -------------------------------------------------------------------------------- /packages/interface/src/request/auth.ts: -------------------------------------------------------------------------------- 1 | import { CreateRequestType } from './base' 2 | import { User } from '../entity' 3 | 4 | export type SignInRequest = CreateRequestType< 5 | null, 6 | null, 7 | { 8 | userID: string 9 | password: string 10 | }, 11 | 'body' 12 | > 13 | 14 | export type SignInResponse = { 15 | user: User 16 | customToken: string 17 | } 18 | -------------------------------------------------------------------------------- /packages/interface/src/request/base.ts: -------------------------------------------------------------------------------- 1 | type RequestKeys = 'param' | 'query' | 'body' 2 | interface BaseRequest { 3 | param: Param 4 | query: Query 5 | body: Body 6 | } 7 | 8 | export type CreateRequestType = Pick, K> 9 | 10 | export type ExtractPropertyType = T[P] 11 | -------------------------------------------------------------------------------- /packages/interface/src/request/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth' 2 | export * from './base' 3 | -------------------------------------------------------------------------------- /packages/interface/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "target": "es5", 5 | "lib": ["dom", "es2018"], 6 | "strict": true, 7 | "strictNullChecks": true, 8 | "noImplicitAny": true, 9 | "noImplicitReturns": true, 10 | "skipLibCheck": true, 11 | "declaration": true, 12 | "declarationMap": true, 13 | "pretty": true, 14 | "newLine": "lf", 15 | "esModuleInterop": true, 16 | "moduleResolution": "node" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/web-antd/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"], 3 | "plugins": [ 4 | ["styled-components", { "ssr": true }], 5 | [ 6 | "import", 7 | { 8 | "libraryName": "antd", 9 | "style": true 10 | } 11 | ] 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/web-antd/.env.tmpl: -------------------------------------------------------------------------------- 1 | GOOGLE_APPLICATION_CREDENTIALS="./firebase.admin.key.json" 2 | FIREBASE_CLIENT_API_KEY="AIzaSyBKu1YQwebiQTU2lkdEy70sB0QfIsample" 3 | API_ORIGIN="http://localhost:3003" -------------------------------------------------------------------------------- /packages/web-antd/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /packages/web-antd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@abyssparanoia/web-antd", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "private": true, 6 | "scripts": { 7 | "start:dev": "next -p 3007", 8 | "start:prod": "next start -p 3007", 9 | "build": "run-p -l build:*", 10 | "build:next": "next build", 11 | "lint": "eslint --fix -c ./.eslintrc.json './src/**/*.{ts,tsx}'" 12 | }, 13 | "dependencies": { 14 | "@abyssparanoia/fixtures": "0.0.1", 15 | "@quentin-sommer/react-useragent": "3.1.1", 16 | "@zeit/next-css": "1.0.1", 17 | "@zeit/next-less": "1.0.1", 18 | "antd": "4.16.13", 19 | "axios": "0.24.0", 20 | "babel-plugin-import": "1.13.3", 21 | "core-js": "3.19.1", 22 | "date-fns": "2.25.0", 23 | "http2": "3.3.7", 24 | "less": "4.1.2", 25 | "less-vars-to-js": "1.3.0", 26 | "moment": "2.29.2", 27 | "next": "10.2.3", 28 | "next-offline": "5.0.5", 29 | "nookies": "2.5.2", 30 | "null-loader": "4.0.1", 31 | "path": "0.12.7", 32 | "query-string": "7.0.1", 33 | "react": "17.0.2", 34 | "react-dom": "17.0.2", 35 | "react-google-button": "0.7.2", 36 | "styled-components": "5.3.3", 37 | "swr": "0.5.7" 38 | }, 39 | "devDependencies": { 40 | "@babel/core": "7.12.10", 41 | "@types/faker": "5.5.9", 42 | "@types/node": "14.17.33", 43 | "@types/react": "17.0.35", 44 | "@types/react-dom": "17.0.11", 45 | "@types/styled-components": "5.1.15", 46 | "@types/ua-parser-js": "0.7.36", 47 | "babel-loader": "8.1.0", 48 | "babel-plugin-styled-components": "1.13.3", 49 | "react-docgen-typescript-loader": "3.7.2" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/web-antd/polyfills.js: -------------------------------------------------------------------------------- 1 | import 'core-js' 2 | 3 | console.log('Load your polyfills') 4 | -------------------------------------------------------------------------------- /packages/web-antd/src/assets/antd-custom.less: -------------------------------------------------------------------------------- 1 | @primary-color: #f0414c; 2 | @layout-header-height: 51px; 3 | @layout-header-padding: 0px 0px; 4 | @layout-header-background: #f8f8f8; 5 | @layout-body-background: #ffffff; 6 | @layout-footer-padding: 0px; 7 | @border-radius-base: 2px; 8 | // radio button 9 | @radio-button-checked-bg: rgba(240, 65, 76, 0.1); 10 | @radio-button-color: #303034; 11 | 12 | // button 13 | @border-color-base: #f0414c; 14 | @btn-default-color: #f0414c; 15 | @btn-border-radius-base: 5px; 16 | @btn-disable-color: #ffffff; 17 | @btn-disable-bg: rgba(240, 65, 76, 0.5); 18 | @btn-disable-border: rgba(240, 65, 76, 0.1); 19 | 20 | // input 21 | @input-color: '#000000'; 22 | @input-border-color: '#d9d9d9'; 23 | 24 | // checkbox 25 | 26 | @checkbox-size: 24px; 27 | @font-family: Noto Sans JP, Roboto, sans-serif; 28 | -------------------------------------------------------------------------------- /packages/web-antd/src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Document, { Head, Main, NextScript, DocumentContext } from 'next/document' 3 | import { ServerStyleSheet } from 'styled-components' 4 | 5 | export default class extends Document { 6 | static getInitialProps = async (ctx: DocumentContext) => { 7 | const styledComponentsSheet = new ServerStyleSheet() 8 | const originalRenderPage = ctx.renderPage 9 | 10 | // main ページのcssをここでレンダリングさせるためのenhancer 11 | ctx.renderPage = () => 12 | originalRenderPage({ 13 | enhanceApp: App => props => ({ 14 | ...styledComponentsSheet.collectStyles() 15 | }) 16 | }) 17 | 18 | const initialProps = await Document.getInitialProps(ctx) 19 | 20 | return { 21 | ...initialProps, 22 | styles: <>{styledComponentsSheet.getStyleElement()} 23 | } 24 | } 25 | 26 | render() { 27 | return ( 28 | 29 | 30 | 31 | {/* Use minimum-scale=1 to enable GPU rasterization */} 32 | 33 | 34 | 38 |