├── .babelrc ├── .editorconfig ├── .eslintrc.js ├── .github └── workflows │ └── jest.yml ├── .gitignore ├── README.md ├── api ├── config.js ├── controllers │ ├── analyticsController.js │ ├── awardsController.js │ ├── brandsController.js │ ├── breweriesController.js │ ├── breweryYearDatasController.js │ ├── commentsController.js │ ├── sakesController.js │ └── usersController.js ├── db.js ├── index.js ├── models │ ├── Award.js │ ├── Brand.js │ ├── Brewery.js │ ├── BreweryYearData.js │ ├── Comment.js │ ├── Package.js │ ├── Sake.js │ ├── SakeReview.js │ └── User.js ├── routes │ ├── analytics.js │ ├── auth.js │ ├── awards.js │ ├── brands.js │ ├── breweries.js │ ├── bydatas.js │ ├── comments.js │ ├── sakes.js │ └── users.js ├── swagger.js └── swagger_output.json ├── assets ├── README.md ├── icons │ ├── hotspring.svg │ └── sake.svg ├── image │ ├── sake-footer_bg.png │ ├── sakepedia-yoko.png │ └── sakepedia.png └── style │ └── app.scss ├── components ├── BrandList.vue ├── BrandSelect.vue ├── BreweriesMap.vue ├── BreweriesMapHotsprings.vue ├── BreweryMap.vue ├── BrewerySelect.vue ├── BreweryYearDataList.vue ├── CommentList.vue ├── FlexTextarea.vue ├── ImageList.vue ├── ImageUploader.vue ├── LastBreweryYearData.vue ├── MariageSelect.vue ├── README.md ├── RangeValue.vue ├── SakeList.vue ├── SakeSelect.vue ├── TagSelect.vue └── mixins │ └── FileEvaluable.vue ├── data └── related_locations │ └── hot_springs.json ├── docker-compose.yml ├── jest.config.js ├── layouts ├── README.md └── default.vue ├── lib └── ApiClient │ └── getList.js ├── middleware ├── README.md └── authenticated.js ├── nuxt.config.js ├── package-lock.json ├── package.json ├── pages ├── README.md ├── about │ └── index.vue ├── auth │ ├── account.vue │ ├── login.vue │ └── logout.vue ├── brands │ ├── _id │ │ ├── index.vue │ │ └── update.vue │ ├── add.vue │ └── index.vue ├── breweries │ ├── _id │ │ ├── index.vue │ │ └── update.vue │ ├── add.vue │ └── index.vue ├── bydatas │ ├── _id │ │ ├── index.vue │ │ ├── update copy.vue │ │ └── update.vue │ ├── add.vue │ └── index.vue ├── callback.vue ├── comments │ ├── _id │ │ ├── index.vue │ │ └── update.vue │ ├── add.vue │ └── index.vue ├── index.vue ├── sakes │ ├── _id │ │ ├── index.vue │ │ └── update.vue │ ├── add.vue │ └── index.vue └── user │ └── contribute.vue ├── plugins ├── README.md ├── moment-filter.js ├── string-filter.js ├── vue2-leaflet-markercluster.js └── vueselect.js ├── server └── index.js ├── static ├── README.md └── favicon.ico ├── store ├── README.md ├── flash.js └── index.js ├── tests ├── components │ ├── BrandList.test.js │ ├── BreweryYearDataList.test.js │ ├── SakeList.test.js │ └── __snapshots__ │ │ ├── BrandList.test.js.snap │ │ ├── BreweryYearDataList.test.js.snap │ │ └── SakeList.test.js.snap ├── lib │ └── ApiClient │ │ └── getList.test.js └── pages │ ├── brands │ ├── __snapshots__ │ │ └── index.test.js.snap │ └── index.test.js │ ├── breweries │ ├── __snapshots__ │ │ └── index.test.js.snap │ └── index.test.js │ └── sakes │ ├── __snapshots__ │ └── index.test.js.snap │ └── index.test.js ├── utils ├── japanese.js └── prefectures.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "presets": [ 5 | [ 6 | "@babel/preset-env", 7 | { 8 | "targets": { 9 | "node": "current" 10 | } 11 | } 12 | ] 13 | ] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true, 6 | }, 7 | parser: 'vue-eslint-parser', 8 | extends: [ 9 | 'plugin:vue/recommended', 10 | 'plugin:nuxt/recommended', 11 | 'prettier', 12 | 'plugin:prettier/recommended', 13 | '@vue/prettier', 14 | ], 15 | plugins: [], 16 | // add your custom rules here 17 | rules: { 18 | 'prettier/prettier': [ 19 | 'error', 20 | { 21 | singleQuote: true, 22 | trailingComma: 'es5', 23 | }, 24 | ], 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /.github/workflows/jest.yml: -------------------------------------------------------------------------------- 1 | name: jest 2 | 3 | on: 4 | - pull_request 5 | 6 | jobs: 7 | build: 8 | name: test 9 | runs-on: ubuntu-latest 10 | steps: 11 | 12 | # Check out the repository 13 | - uses: actions/checkout@v1 14 | 15 | # Install Node.js 16 | - uses: actions/setup-node@v1 17 | with: 18 | node-version: '14.x' 19 | 20 | # Install your dependencies 21 | - run: yarn install 22 | 23 | # Run ESLint 24 | - run: yarn run test:jest 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | /logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # parcel-bundler cache (https://parceljs.org/) 63 | .cache 64 | 65 | # next.js build output 66 | .next 67 | 68 | # nuxt.js build output 69 | .nuxt 70 | 71 | # Nuxt generate 72 | dist 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless 79 | 80 | # IDE / Editor 81 | .idea 82 | 83 | # Service worker 84 | sw.* 85 | 86 | # macOS 87 | .DS_Store 88 | 89 | # Vim swap files 90 | *.swp 91 | 92 | # secret for development 93 | config/.env.* 94 | 95 | mongo/ 96 | tmp/* 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sakepedia Nuxt 2 | 3 | Sakepediaは日本酒のオープンデータを作るためのプラットフォームです。 4 | 5 | 大枠で以下の技術要素を使って開発しています。(詳細はpackege.json参照) 6 | 7 | - Nuxt.js 8 | - Axios/Express 9 | - MongoDB/Mongoose 10 | - Bootstrap-Vue(一部Semantic UI) 11 | - GitHub OAuth App 12 | 13 | ## ブランチについて 14 | 15 | Sakepediaについて、masterをプロテクトして、開発はdevelopで進めることにします。 16 | プルリクエストはdevelopに向けてお願いします。:pray: 17 | 18 | ## Need 19 | 20 | ### Install 21 | - node v14.16.1 22 | - yarn v1.22.5 23 | - mongodb v4.2.8 24 | - pm2 v4.5.6(for server) 25 | 26 | ### Apple Silicon Mac を使う場合 27 | 28 | node-sass v4.14.1は Apple Silicon Mac で使えるようにするには、Nodeのバージョンを^14.16.0にし、node-sassのビルドにPython2系も必要になる。 29 | macOS Monterey 12.3のアップデートでPython2.7がMacOSから削除されたので anyenv 等を使ってインストールする。 30 | 31 | ### GitHub OAuth App(ローカル開発用) 32 | 33 | - https://github.com/settings/developers 34 | - OAuth Appsから新規にOAuth Appsを作成 35 | - Homepage URL: http://localhost:3030 36 | - Authorization callback URL: http://localhost:3030/callback 37 | - 他は任意 38 | - `Client ID`と`Client Secret`を確認 39 | 40 | ## Build Setup 41 | 42 | ``` bash 43 | # node version manage(if you need) 44 | $ brew install n 45 | $ n 14.16 46 | 47 | # yarn(if you need) 48 | $ npm install -g yarn 49 | 50 | # mongodb(if you need) 51 | $ brew tap mongodb/brew 52 | $ brew install mongodb-community 53 | $ brew services start mongodb-community 54 | $ mongo 55 | > use Sakepedia 56 | switched to db Sakepedia 57 | > exit 58 | bye 59 | 60 | # install dependencies 61 | $ yarn install 62 | 63 | # ENV 64 | $ vi config/.env.production 65 | $ vi config/.env.development 66 | 67 | GITHUB_CLIENT_ID=GitHub OAuth AppのClient ID 68 | GITHUB_CLIENT_SECRET=GitHub OAuth AppのClient Secret 69 | BASE_URL=動作させるURL(なければhttp://localhost:3030) 70 | MONGO_CONNECT=MONGODBの接続文字列(例 mongodb://localhost/Sakepedia) 71 | JWT_SECRET=JWTを生成するためのSecret 72 | 73 | # serve with hot reload at localhost:3030 74 | $ yarn dev 75 | 76 | # build for production and launch server 77 | $ yarn build 78 | $ pm2 start npm -- start 79 | 80 | # generate static project 81 | $ yarn generate 82 | ``` 83 | ## run test in local 84 | `yarn run test:jest` 85 | update jest snapshots by running `yarn run test:jest -u` 86 | -------------------------------------------------------------------------------- /api/config.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | const User = require('./models/User'); 3 | 4 | const config = {}; 5 | 6 | module.exports = config; 7 | 8 | module.exports.isAuthenticated = async function (req, res, next) { 9 | if (!req.user) { 10 | const authHeader = req.headers['authorization']; 11 | //HeaderにAuthorizationが定義されているか 12 | if (authHeader !== undefined) { 13 | //Bearerが正しく定義されているか 14 | if (authHeader.split(' ')[0] === 'Bearer') { 15 | try { 16 | const jwToken = jwt.verify( 17 | authHeader.split(' ')[1], 18 | process.env.JWT_SECRET 19 | ); 20 | //ユーザー検索 21 | const currentUser = await User.findOne({ 22 | _id: jwToken.sub, 23 | }); 24 | req.user = currentUser; 25 | } catch (e) { 26 | console.log(e.message); 27 | return res.status(401).json({ error: e.message }); 28 | } 29 | } else { 30 | return res 31 | .status(401) 32 | .json({ error: 'unauthorized (header format error)' }); 33 | } 34 | } else { 35 | return res.status(401).json({ error: 'unauthorized (header error)' }); 36 | } 37 | } 38 | next(); 39 | }; 40 | -------------------------------------------------------------------------------- /api/controllers/analyticsController.js: -------------------------------------------------------------------------------- 1 | const Brewery = require('../models/Brewery'); 2 | const Brand = require('../models/Brand'); 3 | const Sake = require('../models/Sake'); 4 | const User = require('../models/User'); 5 | const Comment = require('../models/Comment'); 6 | 7 | module.exports.summary = async function (req, res) { 8 | const brewery = await Brewery.estimatedDocumentCount(); 9 | const brand = await Brand.estimatedDocumentCount(); 10 | const sake = await Sake.estimatedDocumentCount(); 11 | const user = await User.estimatedDocumentCount(); 12 | const comment = await Comment.estimatedDocumentCount(); 13 | return res.json({ 14 | summary: { 15 | brewery: brewery, 16 | brand: brand, 17 | sake: sake, 18 | user: user, 19 | comment: comment, 20 | }, 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /api/controllers/awardsController.js: -------------------------------------------------------------------------------- 1 | const Award = require('../models/Award'); 2 | const validator = require('express-validator'); 3 | const paginate = require('express-paginate'); 4 | const japanese = require('../../utils/japanese'); 5 | const Brewery = require('../models/Brewery'); 6 | 7 | // Get all 8 | module.exports.all = function (req, res, next) { 9 | var search = { 10 | brewery: req.query.brewery, 11 | }; 12 | Award.paginate( 13 | search, 14 | { page: req.query.page, limit: req.query.limit }, 15 | function (err, result) { 16 | if (err) { 17 | return res.status(500).json({ 18 | message: 'Error getting records.', 19 | }); 20 | } 21 | return res.json({ 22 | awards: result.docs, 23 | currentPage: result.page, 24 | pageCount: result.pages, 25 | pages: paginate.getArrayPages(req)(3, result.pages, req.query.page), 26 | }); 27 | } 28 | ); 29 | }; 30 | 31 | // Get one 32 | module.exports.show = function (req, res) { 33 | var id = req.params.id; 34 | Award.findOne({ _id: id }) 35 | .populate('brewery') 36 | .exec(function (err, data) { 37 | if (err) { 38 | return res.status(500).json({ 39 | message: 'Error getting record.' + err, 40 | }); 41 | } 42 | if (!data) { 43 | return res.status(404).json({ 44 | message: 'No such record', 45 | }); 46 | } 47 | return res.json(data); 48 | }); 49 | }; 50 | 51 | //names 52 | module.exports.list = function (req, res, next) { 53 | var keyword = req.query.keyword; 54 | var search = {}; 55 | if (keyword) { 56 | search = { 57 | $or: [ 58 | { name: new RegExp(keyword, 'i') }, 59 | { kana: new RegExp(japanese.hiraToKana(keyword), 'i') }, 60 | ], 61 | }; 62 | } 63 | Award.find(search) 64 | .select('name') 65 | .limit(10) 66 | .exec(function (err, datas) { 67 | if (err) { 68 | return res.status(500).json({ 69 | message: 'Error getting records. : ' + err, 70 | }); 71 | } 72 | return res.json(datas); 73 | }); 74 | }; 75 | 76 | // Create 77 | module.exports.create = [ 78 | function (req, res) { 79 | // throw validation errors 80 | const errors = validator.validationResult(req); 81 | if (!errors.isEmpty()) { 82 | return res.status(422).json({ errors: errors.mapped() }); 83 | } 84 | 85 | // initialize record 86 | var award = new Award({ 87 | brewery: req.body.brewery, 88 | award: req.body.award, 89 | sakeName: req.body.sakeName, 90 | year: req.body.year, 91 | createdAt: new Date(), 92 | modifiedAt: new Date(), 93 | userId: req.user._id, 94 | }); 95 | 96 | // save record 97 | award.save(function (err, award) { 98 | if (err) { 99 | return res.status(500).json({ 100 | message: 'Error saving record', 101 | error: err, 102 | }); 103 | } 104 | return res.json({ 105 | message: 'saved', 106 | _id: award._id, 107 | }); 108 | }); 109 | }, 110 | ]; 111 | 112 | // Update 113 | module.exports.update = [ 114 | function (req, res) { 115 | // throw validation errors 116 | const errors = validator.validationResult(req); 117 | if (!errors.isEmpty()) { 118 | return res.status(422).json({ errors: errors.mapped() }); 119 | } 120 | 121 | var id = req.params.id; 122 | Award.findOne({ _id: id }, function (err, data) { 123 | if (err) { 124 | return res.status(500).json({ 125 | message: 'Error saving record update sake', 126 | error: err, 127 | }); 128 | } 129 | if (!data) { 130 | return res.status(404).json({ 131 | message: 'No such record', 132 | }); 133 | } 134 | 135 | // initialize record 136 | data.brewery = req.body.brewery ? req.body.brewery : data.brewery; 137 | data.award = req.body.award ? req.body.award : data.award; 138 | data.sakeName = req.body.sakeName ? req.body.sakeName : data.sakeName; 139 | data.year = req.body.year ? req.body.year : data.year; 140 | data.modifiedAt = new Date(); 141 | data.userId = req.user._id; 142 | 143 | // save record 144 | data.save(function (err, data) { 145 | if (err) { 146 | return res.status(500).json({ 147 | message: 'Error getting record update sake.', 148 | error: err, 149 | }); 150 | } 151 | if (!data) { 152 | return res.status(404).json({ 153 | message: 'No such record', 154 | }); 155 | } 156 | return res.json(data); 157 | }); 158 | }); 159 | }, 160 | ]; 161 | 162 | // Delete 163 | module.exports.delete = function (req, res) { 164 | var id = req.params.id; 165 | Award.findByIdAndRemove(id, function (err, data) { 166 | if (err) { 167 | return res.status(500).json({ 168 | message: 'Error getting record.', 169 | error: err, 170 | }); 171 | } 172 | return res.json(data); 173 | }); 174 | }; 175 | -------------------------------------------------------------------------------- /api/controllers/brandsController.js: -------------------------------------------------------------------------------- 1 | const Brand = require('../models/Brand'); 2 | const validator = require('express-validator'); 3 | const paginate = require('express-paginate'); 4 | const japanese = require('../../utils/japanese'); 5 | const Brewery = require('../models/Brewery'); 6 | 7 | // Get all 8 | module.exports.all = function (req, res, next) { 9 | var keyword = req.query.keyword; 10 | var brewery = req.query.brewery; 11 | var search = {}; 12 | 13 | if (brewery) [(search.brewery = brewery)]; 14 | if (keyword) { 15 | search.$or = [ 16 | { name: new RegExp(keyword, 'i') }, 17 | { kana: new RegExp(japanese.kanaToHira(keyword), 'i') }, 18 | ]; 19 | } 20 | Brand.paginate( 21 | search, 22 | { page: req.query.page, limit: req.query.limit }, 23 | function (err, result) { 24 | if (err) { 25 | return res.status(500).json({ 26 | message: 'Error getting records.', 27 | }); 28 | } 29 | return res.json({ 30 | brands: result.docs, 31 | currentPage: result.page, 32 | pageCount: result.pages, 33 | pages: paginate.getArrayPages(req)(3, result.pages, req.query.page), 34 | }); 35 | } 36 | ); 37 | }; 38 | 39 | // Get one 40 | module.exports.show = function (req, res) { 41 | var id = req.params.id; 42 | Brand.findOne({ _id: id }).exec(async function (err, data) { 43 | if (err) { 44 | return res.status(500).json({ 45 | message: 'Error getting record.' + err, 46 | }); 47 | } 48 | if (!data) { 49 | return res.status(404).json({ 50 | message: 'No such record', 51 | }); 52 | } 53 | try { 54 | await data.populate('brewery', 'name').execPopulate(); 55 | } catch (e) {} 56 | return res.json(data); 57 | }); 58 | }; 59 | 60 | //names 61 | module.exports.list = function (req, res, next) { 62 | var keyword = req.query.keyword; 63 | var search = {}; 64 | if (keyword) { 65 | search = { 66 | $or: [ 67 | { name: new RegExp(keyword, 'i') }, 68 | { kana: new RegExp(japanese.hiraToKana(keyword), 'i') }, 69 | ], 70 | }; 71 | } 72 | Brand.find(search) 73 | .select('name') 74 | .limit(10) 75 | .exec(function (err, datas) { 76 | if (err) { 77 | return res.status(500).json({ 78 | message: 'Error getting records. : ' + err, 79 | }); 80 | } 81 | return res.json(datas); 82 | }); 83 | }; 84 | 85 | // Create 86 | module.exports.create = [ 87 | // validations rules 88 | validator.body('name', '名前を入力してください').isLength({ min: 1 }), 89 | //酒蔵ID+銘柄名でユニークにする。 90 | validator.body(['name']).custom((value, { req }) => { 91 | return Brand.findOne({ 92 | name: value, 93 | _id: { $ne: req.params.id }, 94 | brewery: req.body.brewery, 95 | }).then((data) => { 96 | if (data !== null) { 97 | return Promise.reject('すでに存在します'); 98 | } 99 | }); 100 | }), 101 | validator.body('brewery', '酒蔵を入力してください').isLength({ min: 1 }), 102 | 103 | function (req, res) { 104 | // throw validation errors 105 | const errors = validator.validationResult(req); 106 | if (!errors.isEmpty()) { 107 | return res.status(422).json({ errors: errors.mapped() }); 108 | } 109 | 110 | // initialize record 111 | var brand = new Brand({ 112 | name: req.body.name, 113 | kana: req.body.kana ? japanese.kanaToHira(req.body.kana) : '', 114 | description: req.body.description, 115 | logo: req.body.logo, 116 | brewery: req.body.brewery, 117 | userId: req.user._id, 118 | }); 119 | 120 | // save record 121 | brand.save(function (err, brand) { 122 | if (err) { 123 | return res.status(500).json({ 124 | message: 'Error saving record', 125 | error: err, 126 | }); 127 | } 128 | return res.json({ 129 | message: 'saved', 130 | _id: brand._id, 131 | }); 132 | }); 133 | }, 134 | ]; 135 | 136 | // Update 137 | module.exports.update = [ 138 | // validation rules 139 | validator.body('name', '名前を入力してください').isLength({ min: 1 }), 140 | validator.body('name').custom((value, { req }) => { 141 | return Brand.findOne({ name: value, _id: { $ne: req.params.id } }).then( 142 | (data) => { 143 | if (data !== null) { 144 | return Promise.reject('すでに存在します'); 145 | } 146 | } 147 | ); 148 | }), 149 | 150 | function (req, res) { 151 | // throw validation errors 152 | const errors = validator.validationResult(req); 153 | if (!errors.isEmpty()) { 154 | return res.status(422).json({ errors: errors.mapped() }); 155 | } 156 | 157 | var id = req.params.id; 158 | Brand.findOne({ _id: id }, function (err, data) { 159 | if (err) { 160 | return res.status(500).json({ 161 | message: 'Error saving record update sake', 162 | error: err, 163 | }); 164 | } 165 | if (!data) { 166 | return res.status(404).json({ 167 | message: 'No such record', 168 | }); 169 | } 170 | 171 | // initialize record 172 | data.name = req.body.name ? req.body.name : data.name; 173 | data.kana = req.body.kana 174 | ? japanese.kanaToHira(req.body.kana) 175 | : data.kana; 176 | data.logo = req.body.logo !== undefined ? req.body.logo : data.logo; 177 | data.brewery = req.body.brewery ? req.body.brewery : data.brewery; 178 | data.description = 179 | req.body.description !== undefined 180 | ? req.body.description 181 | : data.description; 182 | data.userId = req.user._id; 183 | 184 | // save record 185 | data.save(function (err, data) { 186 | if (err) { 187 | return res.status(500).json({ 188 | message: 'Error getting record update sake.', 189 | error: err, 190 | }); 191 | } 192 | if (!data) { 193 | return res.status(404).json({ 194 | message: 'No such record', 195 | }); 196 | } 197 | return res.json(data); 198 | }); 199 | }); 200 | }, 201 | ]; 202 | 203 | // Delete 204 | module.exports.delete = function (req, res) { 205 | var id = req.params.id; 206 | Brand.findByIdAndRemove(id, function (err, data) { 207 | if (err) { 208 | return res.status(500).json({ 209 | message: 'Error getting record.', 210 | error: err, 211 | }); 212 | } 213 | return res.json(data); 214 | }); 215 | }; 216 | -------------------------------------------------------------------------------- /api/controllers/commentsController.js: -------------------------------------------------------------------------------- 1 | const Brewery = require('../models/Brewery'); 2 | const Brand = require('../models/Brand'); 3 | const Sake = require('../models/Sake'); 4 | const Comment = require('../models/Comment'); 5 | const validator = require('express-validator'); 6 | const paginate = require('express-paginate'); 7 | const japanese = require('../../utils/japanese'); 8 | 9 | // Get all 10 | module.exports.all = function (req, res, next) { 11 | var keyword = req.query.keyword; 12 | var brewery = req.query.brewery; 13 | var brand = req.query.brand; 14 | var sake = req.query.sake; 15 | var userId = req.query.userId; 16 | var search = {}; 17 | 18 | if (brewery) { 19 | search.brewery = brewery; 20 | } 21 | if (brand) { 22 | search.brand = brand; 23 | } 24 | if (sake) { 25 | search.sake = sake; 26 | } 27 | if (userId) { 28 | search.userId = userId; 29 | } 30 | if (keyword) { 31 | search.$or = [ 32 | { comment: new RegExp(keyword, 'i') }, 33 | { comment: new RegExp(japanese.hiraToKana(keyword), 'i') }, 34 | { comment: new RegExp(japanese.kanaToHira(keyword), 'i') }, 35 | ]; 36 | } 37 | Comment.paginate( 38 | search, 39 | { page: req.query.page, limit: req.query.limit }, 40 | function (err, result) { 41 | if (err) { 42 | return res.status(500).json({ 43 | message: 'Error getting records.', 44 | }); 45 | } 46 | return res.json({ 47 | comments: result.docs, 48 | currentPage: result.page, 49 | pageCount: result.pages, 50 | pages: paginate.getArrayPages(req)(3, result.pages, req.query.page), 51 | }); 52 | } 53 | ); 54 | }; 55 | 56 | // Get one 57 | module.exports.show = function (req, res) { 58 | var id = req.params.id; 59 | Comment.findOne({ _id: id }).exec(async function (err, data) { 60 | if (err) { 61 | return res.status(500).json({ 62 | message: 'Error getting record.' + err, 63 | }); 64 | } 65 | if (!data) { 66 | return res.status(404).json({ 67 | message: 'No such record', 68 | }); 69 | } 70 | try { 71 | await data.populate('brewery', 'name').execPopulate(); 72 | } catch (e) {} 73 | try { 74 | await data.populate('brand', 'name').execPopulate(); 75 | } catch (e) {} 76 | try { 77 | await data.populate('sake', 'name').execPopulate(); 78 | } catch (e) {} 79 | return res.json(data); 80 | }); 81 | }; 82 | 83 | // Create 84 | module.exports.create = [ 85 | function (req, res) { 86 | // throw validation errors 87 | const errors = validator.validationResult(req); 88 | if (!errors.isEmpty()) { 89 | return res.status(422).json({ errors: errors.mapped() }); 90 | } 91 | 92 | // initialize record 93 | var comment = new Comment({ 94 | comment: req.body.comment, 95 | image: req.body.image, 96 | brand: req.body.brand, 97 | brewery: req.body.brewery, 98 | sake: req.body.sake, 99 | mariages: req.body.mariages, 100 | userId: req.user._id, 101 | }); 102 | 103 | // save record 104 | comment.save(function (err, data) { 105 | if (err) { 106 | return res.status(500).json({ 107 | message: 'Error saving record', 108 | error: err, 109 | }); 110 | } 111 | return res.json({ 112 | message: 'saved', 113 | _id: data._id, 114 | }); 115 | }); 116 | }, 117 | ]; 118 | 119 | // Update 120 | module.exports.update = [ 121 | // validations rules 122 | validator.body('comment').custom((value, { req }) => { 123 | return Comment.findOne({ _id: req.params.id }).then((comment) => { 124 | if (comment.userId.toString() != req.user._id.toString()) { 125 | return Promise.reject('Not your comment'); 126 | } 127 | }); 128 | }), 129 | 130 | function (req, res) { 131 | // throw validation errors 132 | const errors = validator.validationResult(req); 133 | if (!errors.isEmpty()) { 134 | return res.status(422).json({ errors: errors.mapped() }); 135 | } 136 | var id = req.params.id; 137 | Comment.findOne({ _id: id }, function (err, data) { 138 | if (err) { 139 | return res.status(500).json({ 140 | message: 'Error saving record update comment', 141 | error: err, 142 | }); 143 | } 144 | if (!data) { 145 | return res.status(404).json({ 146 | message: 'No such record', 147 | }); 148 | } 149 | 150 | // initialize record 151 | data.comment = req.body.comment ? req.body.comment : data.comment; 152 | data.image = req.body.image ? req.body.image : data.image; 153 | data.brand = req.body.brand ? req.body.brand : data.brand; 154 | data.brewery = req.body.brewery ? req.body.brewery : data.brewery; 155 | data.sake = req.body.sake ? req.body.sake : data.sake; 156 | data.mariages = req.body.mariages ? req.body.mariages : data.mariages; 157 | 158 | // save record 159 | data.save(function (err, data) { 160 | if (err) { 161 | return res.status(500).json({ 162 | message: 'Error getting record update comment.', 163 | error: err, 164 | }); 165 | } 166 | if (!data) { 167 | return res.status(404).json({ 168 | message: 'No such record', 169 | }); 170 | } 171 | return res.json(data); 172 | }); 173 | }); 174 | }, 175 | ]; 176 | 177 | // Delete 178 | module.exports.delete = function (req, res) { 179 | var id = req.params.id; 180 | Comment.findByIdAndRemove(id, function (err, data) { 181 | if (err) { 182 | return res.status(500).json({ 183 | message: 'Error getting record.', 184 | error: err, 185 | }); 186 | } 187 | return res.json(data); 188 | }); 189 | }; 190 | -------------------------------------------------------------------------------- /api/controllers/usersController.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | 3 | const User = require('../models/User'); 4 | const Brewery = require('../models/Brewery'); 5 | const Brand = require('../models/Brand'); 6 | const Sake = require('../models/Sake'); 7 | const Comment = require('../models/Comment'); 8 | 9 | module.exports.create = async function ( 10 | type, 11 | identity, 12 | avatarUrl, 13 | username = null 14 | ) { 15 | const user = new User({ 16 | type: type, 17 | identity: identity, 18 | avatarUrl: avatarUrl, 19 | name: username, 20 | gitUsername: username, 21 | }); 22 | const saved = await user.save(); 23 | return saved; 24 | }; 25 | 26 | module.exports.update = async function (req, res) { 27 | if (req.body.name === '') { 28 | return res.json({ error: '名前を入力してください' }); 29 | } 30 | const user = await User.findOne({ _id: req.params.id }); 31 | if (await User.findOne({ name: req.body.name })) { 32 | return res.json({ error: 'その名前はすでに使用されています' }); 33 | } 34 | user.name = req.body.name; 35 | user.save((err, user) => { 36 | if (err) { 37 | return res.json({ error: err }); 38 | } 39 | return res.json({ user: user }); 40 | }); 41 | }; 42 | 43 | module.exports.show = async function (req, res) { 44 | const user = await User.findOne({ _id: req.params.id }); 45 | return res.json({ name: user.name }); 46 | }; 47 | 48 | module.exports.jwt = async function (req, res) { 49 | const jwtoken = jwt.sign({ sub: req.user._id }, process.env.JWT_SECRET, { 50 | expiresIn: '1h', 51 | }); 52 | var dt = new Date(); 53 | dt.setHours(dt.getHours() + 1); 54 | return res.json({ jwt: jwtoken, expiresIn: dt }); 55 | }; 56 | 57 | module.exports.contribute = async function (req, res) { 58 | const brewery = await Brewery.estimatedDocumentCount({ 59 | userId: req.params.id, 60 | }); 61 | const brand = await Brand.estimatedDocumentCount({ userId: req.params.id }); 62 | const sake = await Sake.estimatedDocumentCount({ userId: req.params.id }); 63 | const comment = await Comment.estimatedDocumentCount({ 64 | userId: req.params.id, 65 | }); 66 | return res.json({ 67 | contribute: { 68 | brewery: brewery, 69 | brand: brand, 70 | sake: sake, 71 | comment: comment, 72 | }, 73 | }); 74 | }; 75 | -------------------------------------------------------------------------------- /api/db.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | mongoose.connect(process.env.MONGO_CONNECT, { 3 | useNewUrlParser: true, 4 | useUnifiedTopology: true, 5 | useFindAndModify: false, 6 | useCreateIndex: true, 7 | }); 8 | var db = mongoose.connection; 9 | db.on('error', console.error.bind(console, 'connection error:')); 10 | db.once('open', function callback() { 11 | console.log('MongoDB Connected...'); 12 | }); 13 | 14 | module.exports = db; 15 | -------------------------------------------------------------------------------- /api/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import session from 'express-session'; 3 | 4 | const db = require('./db'); 5 | const swaggerUi = require('swagger-ui-express'); 6 | const swaggerFile = require('./swagger_output.json'); 7 | 8 | // Create express instnace 9 | const app = express(); 10 | 11 | app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile)); 12 | 13 | // Init body-parser options (inbuilt with express) 14 | app.use(express.json({ limit: '50mb' })); 15 | app.use(express.urlencoded({ limit: '50mb', extended: true })); 16 | 17 | // sessionの設定 18 | app.use( 19 | session({ 20 | secret: process.env.SESSION_SECRET || 'secret', 21 | resave: true, 22 | saveUninitialized: true, 23 | cookie: { 24 | secure: 'auto', 25 | }, 26 | }) 27 | ); 28 | 29 | // Require & Import API routes 30 | const auth = require('./routes/auth'); 31 | const awards = require('./routes/awards'); 32 | const breweries = require('./routes/breweries'); 33 | const brands = require('./routes/brands'); 34 | const sakes = require('./routes/sakes'); 35 | const bydatas = require('./routes/bydatas'); 36 | const comments = require('./routes/comments'); 37 | const users = require('./routes/users'); 38 | const analytics = require('./routes/analytics'); 39 | 40 | //Authenticate 41 | app.use(auth); 42 | 43 | // Use API Routes 44 | //app.use(users) 45 | app.use(awards); 46 | app.use(breweries); 47 | app.use(brands); 48 | app.use(sakes); 49 | app.use(bydatas); 50 | app.use(comments); 51 | app.use(users); 52 | app.use(analytics); 53 | 54 | // Export the server middleware 55 | module.exports = { 56 | path: '/api', 57 | handler: app, 58 | }; 59 | -------------------------------------------------------------------------------- /api/models/Award.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const mongoosePaginate = require('mongoose-paginate'); 3 | const Schema = mongoose.Schema; 4 | 5 | const Award = new Schema({ 6 | brewery: { type: Schema.Types.ObjectId, ref: 'Brewery', required: true }, 7 | award: { type: String, required: true }, 8 | sakeName: { type: String }, 9 | year: { type: Number, required: true }, 10 | createdAt: { type: Date }, 11 | modifiedAt: { type: Date }, 12 | author: { type: String }, 13 | userId: { type: Schema.Types.ObjectId, ref: 'User' }, 14 | }); 15 | 16 | Award.plugin(mongoosePaginate); 17 | module.exports = mongoose.model('Award', Award); 18 | -------------------------------------------------------------------------------- /api/models/Brand.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const mongoosePaginate = require('mongoose-paginate'); 3 | const Schema = mongoose.Schema; 4 | 5 | const Brand = new Schema( 6 | { 7 | name: { type: String, required: true }, 8 | kana: { type: String }, 9 | brewery: { type: Schema.Types.ObjectId, ref: 'Brewery', required: true }, 10 | logo: { type: String }, 11 | description: { type: String }, 12 | author: { type: String }, 13 | userId: { type: Schema.Types.ObjectId, ref: 'User' }, 14 | }, 15 | { timestamps: true } 16 | ); 17 | 18 | Brand.virtual('brandId').get(function () { 19 | return this._id; 20 | }); 21 | 22 | Brand.plugin(mongoosePaginate); 23 | module.exports = mongoose.model('Brand', Brand); 24 | -------------------------------------------------------------------------------- /api/models/Brewery.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const mongoosePaginate = require('mongoose-paginate'); 3 | const Schema = mongoose.Schema; 4 | 5 | const Brewery = new Schema( 6 | { 7 | breweryId: { type: Number, index: { unique: true } }, 8 | name: { type: String, required: true }, 9 | kana: { type: String }, 10 | prefecture: { type: String }, 11 | address: { type: String }, 12 | latitude: { type: Number }, 13 | longitude: { type: Number }, 14 | email: { type: String }, 15 | tel: { type: String }, 16 | fax: { type: String }, 17 | url: { type: String }, 18 | ecurl: { type: String }, 19 | facebook: { type: String }, 20 | twitter: { type: String }, 21 | instagram: { type: String }, 22 | othersns: { type: String }, 23 | startYear: { type: String }, 24 | endYear: { type: String }, 25 | visit: { type: String }, 26 | tasting: { type: String }, 27 | cafe: { type: String }, 28 | shop: { type: String }, 29 | otherBrewing: { type: String }, 30 | author: { type: String }, 31 | userId: { type: Schema.Types.ObjectId, ref: 'User' }, 32 | }, 33 | { timestamps: true } 34 | ); 35 | 36 | Brewery.plugin(mongoosePaginate); 37 | module.exports = mongoose.model('Brewery', Brewery); 38 | -------------------------------------------------------------------------------- /api/models/BreweryYearData.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const mongoosePaginate = require('mongoose-paginate'); 3 | const Schema = mongoose.Schema; 4 | 5 | const BreweryYearData = new Schema( 6 | { 7 | sake: { type: Schema.Types.ObjectId, ref: 'Sake', required: true }, 8 | makedBY: { type: Number, required: true }, 9 | aminoAcidContent: { type: [Number, Number] }, 10 | alcoholContent: { type: [Number, Number] }, 11 | sakeMeterValue: { type: [Number, Number] }, 12 | acidity: { type: [Number, Number] }, 13 | ricePolishingRate: { type: [Number, Number] }, 14 | sakeYeast: { type: String }, 15 | riceForMakingKoji: { type: String }, 16 | sakeRiceExceptForKojiMaking: { type: String }, 17 | bottledDate: { type: Date }, 18 | author: { type: String }, 19 | userId: { type: Schema.Types.ObjectId, ref: 'User' }, 20 | }, 21 | { timestamps: true } 22 | ); 23 | 24 | BreweryYearData.plugin(mongoosePaginate); 25 | module.exports = mongoose.model('BreweryYearData', BreweryYearData); 26 | -------------------------------------------------------------------------------- /api/models/Comment.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const mongoosePaginate = require('mongoose-paginate'); 3 | const Schema = mongoose.Schema; 4 | 5 | const Comment = new Schema( 6 | { 7 | comment: { type: String }, 8 | image: { type: String }, 9 | brand: { type: Schema.Types.ObjectId, ref: 'Brand' }, 10 | brewery: { type: Schema.Types.ObjectId, ref: 'Brewery' }, 11 | sake: { type: Schema.Types.ObjectId, ref: 'Sake' }, 12 | mariages: { type: [String] }, 13 | author: { type: String }, 14 | userId: { type: Schema.Types.ObjectId, ref: 'User' }, 15 | }, 16 | { timestamps: true } 17 | ); 18 | Comment.virtual('commentId').get(function () { 19 | return this._id; 20 | }); 21 | Comment.plugin(mongoosePaginate); 22 | module.exports = mongoose.model('Comment', Comment); 23 | -------------------------------------------------------------------------------- /api/models/Package.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const mongoosePaginate = require('mongoose-paginate'); 3 | const Schema = mongoose.Schema; 4 | 5 | const Package = new Schema({ 6 | sake: { type: Schema.Types.ObjectId, ref: 'Sake', required: true }, 7 | volume: { type: Number }, 8 | price: { type: Number }, 9 | createdAt: { type: Date }, 10 | modifiedAt: { type: Date }, 11 | author: { type: String }, 12 | userId: { type: Schema.Types.ObjectId, ref: 'User' }, 13 | }); 14 | 15 | Package.plugin(mongoosePaginate); 16 | module.exports = mongoose.model('Package', Package); 17 | -------------------------------------------------------------------------------- /api/models/Sake.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const mongoosePaginate = require('mongoose-paginate'); 3 | const Schema = mongoose.Schema; 4 | 5 | const Sake = new Schema( 6 | { 7 | name: { type: String, required: true, index: { unique: true } }, 8 | kana: { type: String }, 9 | brand: { type: Schema.Types.ObjectId, ref: 'Brand' }, 10 | brewery: { type: Schema.Types.ObjectId, ref: 'Brewery' }, 11 | subname: { type: String }, 12 | type: { type: [String] }, 13 | mariages: { type: [String] }, 14 | description: { type: String }, 15 | url: { type: String }, 16 | author: { type: String }, 17 | userId: { type: Schema.Types.ObjectId, ref: 'User' }, 18 | }, 19 | { timestamps: true } 20 | ); 21 | Sake.virtual('sakeId').get(function () { 22 | return this._id; 23 | }); 24 | Sake.plugin(mongoosePaginate); 25 | module.exports = mongoose.model('Sake', Sake); 26 | -------------------------------------------------------------------------------- /api/models/SakeReview.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const SakeReview = new Schema({ 5 | sake: { type: Schema.Types.ObjectId, ref: 'Sake', required: true }, 6 | mariage: { type: String }, 7 | matchDrinkingVessel: { type: String }, 8 | matchDrinkingSceneAndTarget: { type: String }, 9 | matchDrinkingTemperature: { type: [Number, Number] }, 10 | stars: { type: Number }, 11 | aroma: { type: Number }, 12 | taste: { type: Number }, 13 | description: { type: String }, 14 | createdAt: { type: Date }, 15 | modifiedAt: { type: Date }, 16 | author: { type: String, required: true }, 17 | userId: { type: Schema.Types.ObjectId, ref: 'User' }, 18 | }); 19 | 20 | module.exports = mongoose.model('SakeReview', SakeReview); 21 | -------------------------------------------------------------------------------- /api/models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const User = new Schema( 5 | { 6 | type: { 7 | type: String, 8 | required: true, 9 | }, 10 | identity: { 11 | // type = git ならid, type = google ならsub 12 | type: String, 13 | required: true, 14 | }, 15 | avatarUrl: { 16 | type: String, 17 | }, 18 | name: { 19 | type: String, 20 | unique: true, 21 | }, 22 | gitUsername: { 23 | type: String, 24 | }, 25 | message: { 26 | type: String, 27 | }, 28 | }, 29 | { timestamps: true } 30 | ); 31 | User.index({ type: '1', identity: '1' }, { unique: true }); 32 | module.exports = mongoose.model('User', User); 33 | -------------------------------------------------------------------------------- /api/routes/analytics.js: -------------------------------------------------------------------------------- 1 | const config = require('../config'); 2 | const { Router } = require('express'); 3 | 4 | const router = Router(); 5 | 6 | // Initialize Controller 7 | const analyticsController = require('../controllers/analyticsController'); 8 | 9 | // Update 10 | router.get('/analytics/summary', analyticsController.summary); 11 | 12 | module.exports = router; 13 | -------------------------------------------------------------------------------- /api/routes/auth.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport'; 2 | import { Strategy } from 'passport-github'; 3 | const User = require('../models/User'); 4 | const usersController = require('../controllers/usersController'); 5 | 6 | const config = require('../config'); 7 | const { Router } = require('express'); 8 | 9 | const router = Router(); 10 | 11 | // Passport.jsの設定 12 | router.use(passport.initialize()); 13 | router.use(passport.session()); 14 | 15 | passport.use( 16 | new Strategy( 17 | { 18 | clientID: process.env.GITHUB_CLIENT_ID, 19 | clientSecret: process.env.GITHUB_CLIENT_SECRET, 20 | }, 21 | async (accessToken, refreshToken, profile, done) => { 22 | const user = await User.findOne({ type: 'git', identity: profile.id }); 23 | if (user === null) { 24 | const currentUser = await usersController.create( 25 | 'git', 26 | profile.id, 27 | profile.photos[0].value, 28 | profile.username 29 | ); 30 | return done(null, currentUser); 31 | } else { 32 | return done(null, user); 33 | } 34 | } 35 | ) 36 | ); 37 | 38 | passport.serializeUser((currentUser, done) => { 39 | done(null, { 40 | _id: currentUser._id, 41 | }); 42 | }); 43 | passport.deserializeUser(async (obj, done) => { 44 | const user = await User.findOne({ _id: obj._id }); 45 | done(null, user); 46 | }); 47 | 48 | router.get('/session', (req, res) => { 49 | console.log('/session req.user', req.user); 50 | res.json({ user: req.user }); 51 | }); 52 | 53 | router.get( 54 | '/auth/login', 55 | passport.authenticate('github', { scope: ['user:email'] }) 56 | ); 57 | router.get('/auth/callback', passport.authenticate('github'), (req, res) => { 58 | res.json({ user: req.user }); 59 | }); 60 | router.get('/auth/logout', (req, res) => { 61 | req.logout(); 62 | res.json({}); 63 | }); 64 | 65 | module.exports = router; 66 | -------------------------------------------------------------------------------- /api/routes/awards.js: -------------------------------------------------------------------------------- 1 | const config = require('../config'); 2 | const { Router } = require('express'); 3 | const paginate = require('express-paginate'); 4 | 5 | const router = Router(); 6 | 7 | // Initialize Controller 8 | const awardsController = require('../controllers/awardsController'); 9 | 10 | // Get All 11 | router.get('/awards', paginate.middleware(10, 50), awardsController.all); 12 | 13 | // Get One 14 | router.get('/awards/:id', awardsController.show); 15 | 16 | // List 17 | router.get('/list/awards', awardsController.list); 18 | 19 | // Create 20 | router.post('/awards', config.isAuthenticated, awardsController.create); 21 | 22 | // Update 23 | router.put('/awards/:id', config.isAuthenticated, awardsController.update); 24 | 25 | // Delete 26 | router.delete('/awards/:id', config.isAuthenticated, awardsController.delete); 27 | 28 | module.exports = router; 29 | -------------------------------------------------------------------------------- /api/routes/brands.js: -------------------------------------------------------------------------------- 1 | const config = require('../config'); 2 | const { Router } = require('express'); 3 | const paginate = require('express-paginate'); 4 | 5 | const router = Router(); 6 | 7 | // Initialize Controller 8 | const brandsController = require('../controllers/brandsController'); 9 | 10 | // Get All 11 | router.get('/brands', paginate.middleware(10, 50), brandsController.all); 12 | 13 | // Get One 14 | router.get('/brands/:id', brandsController.show); 15 | 16 | // List 17 | router.get('/list/brands', brandsController.list); 18 | 19 | // Create 20 | router.post('/brands', config.isAuthenticated, brandsController.create); 21 | 22 | // Update 23 | router.put('/brands/:id', config.isAuthenticated, brandsController.update); 24 | 25 | // Delete 26 | router.delete('/brands/:id', config.isAuthenticated, brandsController.delete); 27 | 28 | module.exports = router; 29 | -------------------------------------------------------------------------------- /api/routes/breweries.js: -------------------------------------------------------------------------------- 1 | const config = require('../config'); 2 | const { Router } = require('express'); 3 | const paginate = require('express-paginate'); 4 | 5 | const router = Router(); 6 | 7 | // Initialize Controller 8 | const breweriesController = require('../controllers/breweriesController'); 9 | 10 | // Get All 11 | router.get('/breweries', paginate.middleware(10, 50), breweriesController.all); 12 | 13 | // Get One 14 | router.get('/breweries/:id', breweriesController.show); 15 | 16 | // List 17 | router.get('/list/breweries', breweriesController.list); 18 | 19 | // Get Locations of Breweries 20 | router.get('/locations/breweries', breweriesController.getLocations); 21 | 22 | // Get Locations of Breweries Surrounding Input Positions 23 | router.post( 24 | '/locations/breweries', 25 | breweriesController.getLocationsSurroundingInputPositions 26 | ); 27 | 28 | // Create 29 | router.post('/breweries', config.isAuthenticated, breweriesController.create); 30 | 31 | // Update 32 | router.put( 33 | '/breweries/:id', 34 | config.isAuthenticated, 35 | breweriesController.update 36 | ); 37 | 38 | // Delete 39 | router.delete( 40 | '/breweries/:id', 41 | config.isAuthenticated, 42 | breweriesController.delete 43 | ); 44 | 45 | // Update Locations and Return None Location Breweries 46 | router.get('/updateLocations/breweries', breweriesController.updateLocation); 47 | 48 | module.exports = router; 49 | -------------------------------------------------------------------------------- /api/routes/bydatas.js: -------------------------------------------------------------------------------- 1 | const config = require('../config'); 2 | const { Router } = require('express'); 3 | const paginate = require('express-paginate'); 4 | 5 | const router = Router(); 6 | 7 | // Initialize Controller 8 | const breweryYearDatasController = require('../controllers/breweryYearDatasController'); 9 | 10 | // Get All 11 | router.get( 12 | '/bydatas', 13 | paginate.middleware(10, 50), 14 | breweryYearDatasController.all 15 | ); 16 | 17 | // Get One 18 | router.get('/bydatas/:id', breweryYearDatasController.show); 19 | 20 | // List 21 | router.get('/list/bydatas', breweryYearDatasController.list); 22 | 23 | // Create 24 | router.post( 25 | '/bydatas', 26 | config.isAuthenticated, 27 | breweryYearDatasController.create 28 | ); 29 | 30 | // Update 31 | router.put( 32 | '/bydatas/:id', 33 | config.isAuthenticated, 34 | breweryYearDatasController.update 35 | ); 36 | 37 | // Delete 38 | router.delete( 39 | '/bydatas/:id', 40 | config.isAuthenticated, 41 | breweryYearDatasController.delete 42 | ); 43 | 44 | module.exports = router; 45 | -------------------------------------------------------------------------------- /api/routes/comments.js: -------------------------------------------------------------------------------- 1 | const config = require('../config'); 2 | const { Router } = require('express'); 3 | const paginate = require('express-paginate'); 4 | 5 | const router = Router(); 6 | 7 | // Initialize Controller 8 | const commentsController = require('../controllers/commentsController'); 9 | 10 | // Get All 11 | router.get('/comments', paginate.middleware(10, 50), commentsController.all); 12 | 13 | // Get One 14 | router.get('/comments/:id', commentsController.show); 15 | 16 | // Create 17 | router.post('/comments', config.isAuthenticated, commentsController.create); 18 | 19 | // Update 20 | router.put('/comments/:id', config.isAuthenticated, commentsController.update); 21 | 22 | // Delete 23 | router.delete( 24 | '/comments/:id', 25 | config.isAuthenticated, 26 | commentsController.delete 27 | ); 28 | 29 | module.exports = router; 30 | -------------------------------------------------------------------------------- /api/routes/sakes.js: -------------------------------------------------------------------------------- 1 | const config = require('../config'); 2 | const { Router } = require('express'); 3 | const paginate = require('express-paginate'); 4 | 5 | const router = Router(); 6 | 7 | // Initialize Controller 8 | const sakesController = require('../controllers/sakesController'); 9 | 10 | // Get All 11 | router.get('/sakes', paginate.middleware(10, 50), sakesController.all); 12 | 13 | // Get One 14 | router.get('/sakes/:id', sakesController.show); 15 | 16 | // List 17 | router.get('/list/sakes', sakesController.list); 18 | 19 | // Create 20 | router.post('/sakes', config.isAuthenticated, sakesController.create); 21 | 22 | // Update 23 | router.put('/sakes/:id', config.isAuthenticated, sakesController.update); 24 | 25 | // Delete 26 | router.delete('/sakes/:id', config.isAuthenticated, sakesController.delete); 27 | 28 | //change 29 | router.get('/change/sakes', sakesController.change); 30 | 31 | module.exports = router; 32 | -------------------------------------------------------------------------------- /api/routes/users.js: -------------------------------------------------------------------------------- 1 | const config = require('../config'); 2 | const { Router } = require('express'); 3 | 4 | const router = Router(); 5 | 6 | // Initialize Controller 7 | const usersController = require('../controllers/usersController'); 8 | 9 | 10 | // Update 11 | // issue148対応:getはログイン不要とする 12 | router.get('/users/:id/name', usersController.show); 13 | router.post('/users/:id/name', config.isAuthenticated, usersController.update); 14 | router.get('/users/:id/jwt', config.isAuthenticated, usersController.jwt); 15 | router.get( 16 | '/users/:id/contribute', 17 | config.isAuthenticated, 18 | usersController.contribute 19 | ); 20 | 21 | module.exports = router; 22 | -------------------------------------------------------------------------------- /api/swagger.js: -------------------------------------------------------------------------------- 1 | const swaggerAutogen = require('swagger-autogen')(); 2 | 3 | const doc = { 4 | info: { 5 | title: 'Sakepedia API Doc', 6 | description: 7 | 'Sakepediaで提供しているAPI', 8 | }, 9 | host: 'sakepedia.code4sake.org/api', 10 | schemes: ['http'], 11 | }; 12 | 13 | const outputFile = './swagger_output.json'; 14 | const endpointsFiles = [ 15 | './routes/breweries.js', 16 | './routes/brands.js', 17 | './routes/sakes.js', 18 | './routes/comments.js', 19 | ]; 20 | 21 | swaggerAutogen(outputFile, endpointsFiles, doc); 22 | -------------------------------------------------------------------------------- /assets/README.md: -------------------------------------------------------------------------------- 1 | # ASSETS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your un-compiled assets such as LESS, SASS, or JavaScript. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked). 8 | -------------------------------------------------------------------------------- /assets/icons/hotspring.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 20 | 28 | 36 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /assets/icons/sake.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 14 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /assets/image/sake-footer_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Code-for-SAKE/Sakepedia-Nuxt/7b9871792990c4c80f5e50ccc63cfa0c8343ab8e/assets/image/sake-footer_bg.png -------------------------------------------------------------------------------- /assets/image/sakepedia-yoko.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Code-for-SAKE/Sakepedia-Nuxt/7b9871792990c4c80f5e50ccc63cfa0c8343ab8e/assets/image/sakepedia-yoko.png -------------------------------------------------------------------------------- /assets/image/sakepedia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Code-for-SAKE/Sakepedia-Nuxt/7b9871792990c4c80f5e50ccc63cfa0c8343ab8e/assets/image/sakepedia.png -------------------------------------------------------------------------------- /assets/style/app.scss: -------------------------------------------------------------------------------- 1 | $white: #ffffff; 2 | $dark: #323642; 3 | $primary: #004761; 4 | $warning: #E8D058; 5 | $danger: #823E73; 6 | $success: #8CBD21; 7 | $light: #0C8BBA; 8 | $more-light: #82CFD3; 9 | $info: #F4E9AF; 10 | 11 | $secondary: $primary; 12 | 13 | @import '~bootstrap/scss/bootstrap'; 14 | 15 | :root { 16 | --sidebar-width: 240px; 17 | --content-width: calc(100vw - var(--sidebar-width)); 18 | --header-height: 60px; 19 | --footer-height: 150px; 20 | --content-padding: 20px; 21 | --method-area-padding: 3vw; 22 | --sp2: 2px; 23 | --sp4: 4px; 24 | --sp12: 12px; 25 | --sp16: 16px; 26 | --sp20: 20px; 27 | --gray100: #e3e8ee; 28 | --gray300: #a3acb9; 29 | --gray500: #697386; 30 | --blue500: #556cd6; 31 | --blue50: #f5fbff; 32 | --border-radius-8: 8px; 33 | --font14: 14px; 34 | --background-color: rgb(249, 250, 251); 35 | --table-td-padding: 11px; 36 | 37 | --cluster-small: rgba(140, 189, 33, 0.4); 38 | --cluster-medium: rgba(232, 208, 88, 0.4); 39 | --cluster-large: rgba(130, 62, 115, 0.4); 40 | } 41 | 42 | #__nuxt, #__layout, 43 | body { 44 | height: 100vh; 45 | color: $dark; 46 | } 47 | 48 | #wrap { 49 | min-height: 100vh; 50 | display: flex; 51 | flex-direction: column; 52 | justify-content: space-between; 53 | } 54 | 55 | #container { 56 | min-height: calc(100vh - var(--header-height) - var(--footer-height)); 57 | } 58 | 59 | #header { 60 | text-align: left; 61 | transition: .5s; 62 | width: 100vw; 63 | min-height: var(--header-height); 64 | z-index: 1000; 65 | 66 | .navbar { 67 | width: 100vw; 68 | } 69 | } 70 | 71 | #header.hide{ 72 | top: calc(var(--header-height) * -1); 73 | } 74 | 75 | #footer { 76 | display: flex; 77 | justify-content: center; 78 | min-height: var(--footer-height); 79 | } 80 | 81 | a { 82 | font-weight: 500; 83 | text-decoration: none; 84 | color: $light; 85 | } 86 | 87 | -------------------------------------------------------------------------------- /components/BrandList.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 72 | -------------------------------------------------------------------------------- /components/BrandSelect.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 131 | -------------------------------------------------------------------------------- /components/BreweryMap.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 67 | 68 | 76 | 140 | -------------------------------------------------------------------------------- /components/BrewerySelect.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 135 | -------------------------------------------------------------------------------- /components/BreweryYearDataList.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 73 | -------------------------------------------------------------------------------- /components/CommentList.vue: -------------------------------------------------------------------------------- 1 | 16 | 83 | -------------------------------------------------------------------------------- /components/FlexTextarea.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 47 | 48 | 89 | -------------------------------------------------------------------------------- /components/ImageList.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 52 | 53 | 102 | -------------------------------------------------------------------------------- /components/ImageUploader.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 133 | 134 | 139 | -------------------------------------------------------------------------------- /components/LastBreweryYearData.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 108 | -------------------------------------------------------------------------------- /components/MariageSelect.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 44 | -------------------------------------------------------------------------------- /components/README.md: -------------------------------------------------------------------------------- 1 | # COMPONENTS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | The components directory contains your Vue.js Components. 6 | 7 | _Nuxt.js doesn't supercharge these components._ 8 | -------------------------------------------------------------------------------- /components/RangeValue.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 23 | -------------------------------------------------------------------------------- /components/SakeList.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 72 | -------------------------------------------------------------------------------- /components/SakeSelect.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 129 | -------------------------------------------------------------------------------- /components/TagSelect.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 57 | -------------------------------------------------------------------------------- /components/mixins/FileEvaluable.vue: -------------------------------------------------------------------------------- 1 | 74 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | mongo: 3 | container_name: mongo 4 | restart: always 5 | image: mongo:4.2.8 6 | ports: 7 | - "27017:27017" 8 | volumes: 9 | - ./mongo/db:/data/db 10 | - ./mongo/configdb:/data/configdb 11 | - ./mongo/init:/docker-entrypoint-initdb.d 12 | environment: 13 | MONGO_INITDB_DATABASE: Sakepedia 14 | TZ: Asia/Tokyo 15 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | '^.+\\.js$': 'babel-jest', 4 | '.*\\.(vue)$': 'vue-jest', 5 | }, 6 | moduleNameMapper: { 7 | '^@/(.*)$': '/$1', 8 | }, 9 | moduleFileExtensions: ['js', 'json', 'vue'], 10 | }; 11 | -------------------------------------------------------------------------------- /layouts/README.md: -------------------------------------------------------------------------------- 1 | # LAYOUTS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Application Layouts. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts). 8 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 95 | 96 | 112 | -------------------------------------------------------------------------------- /lib/ApiClient/getList.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export async function getList(type, params, context = undefined) { 4 | if (params.searchName) { 5 | params['keyword'] = params.searchName; 6 | params.searchName = ''; 7 | } 8 | 9 | // paramsのプロパティのうちundefeindなものを取り除く 10 | // なくても動く気がする 11 | let queryParam = {}; 12 | Object.keys(params) 13 | .filter((key) => { 14 | return params[key] !== ''; 15 | }) 16 | .forEach((key) => { 17 | queryParam[key] = params[key]; 18 | }); 19 | 20 | const get = async () => { 21 | if (context) { 22 | return await context.$axios 23 | .get(`/api/${type}`, { params: queryParam }) 24 | .then((res) => { 25 | return res; 26 | }) 27 | .catch((e) => console.log(e)); 28 | } else { 29 | return await axios 30 | .get(`/api/${type}`, { params: queryParam }) 31 | .then((res) => { 32 | return res; 33 | }) 34 | .catch((e) => console.log(e)); 35 | } 36 | }; 37 | 38 | const response = await get(); 39 | 40 | const list = response.data[type]; 41 | const currentPage = response.data.currentPage; 42 | const count = response.data.pageCount; 43 | 44 | return { list, currentPage, count }; 45 | } 46 | -------------------------------------------------------------------------------- /middleware/README.md: -------------------------------------------------------------------------------- 1 | # MIDDLEWARE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your application middleware. 6 | Middleware let you define custom functions that can be run before rendering either a page or a group of pages. 7 | 8 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware). 9 | -------------------------------------------------------------------------------- /middleware/authenticated.js: -------------------------------------------------------------------------------- 1 | export default function ({ store, redirect }) { 2 | // ユーザーが認証されていないとき 3 | if (!store.state.auth) { 4 | console.log("don't login"); 5 | return redirect('/auth/login'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /nuxt.config.js: -------------------------------------------------------------------------------- 1 | const envPath = `config/.env.${process.env.NODE_ENV}`; 2 | require('dotenv').config({ path: envPath }); 3 | 4 | module.exports = { 5 | telemetry: false, 6 | /* 7 | ** Headers of the page 8 | */ 9 | head: { 10 | title: 'Sakepedia Nuxt', 11 | meta: [ 12 | { charset: 'utf-8' }, 13 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 14 | { 15 | hid: 'description', 16 | name: 'description', 17 | content: process.env.npm_package_description || '', 18 | }, 19 | ], 20 | link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }], 21 | }, 22 | /* 23 | ** Customize the progress-bar color 24 | */ 25 | loading: { color: '#fff' }, 26 | /* 27 | ** Global CSS 28 | */ 29 | css: ['@/assets/style/app.scss'], 30 | /* 31 | ** Plugins to load before mounting the App 32 | */ 33 | plugins: [ 34 | '@/plugins/vueselect.js', 35 | '@/plugins/moment-filter.js', 36 | '@/plugins/string-filter.js', 37 | { src: '@/plugins/vue2-leaflet-markercluster.js', mode: 'client' }, 38 | ], 39 | /* 40 | ** Nuxt.js dev-modules 41 | */ 42 | buildModules: ['@nuxtjs/moment'], 43 | /* 44 | ** Nuxt.js modules 45 | */ 46 | modules: [ 47 | // Doc: https://bootstrap-vue.js.org 48 | ['bootstrap-vue/nuxt', { icons: true }], 49 | // Doc: https://axios.nuxtjs.org/usage 50 | '@nuxtjs/axios', 51 | 'semantic-ui-vue/nuxt', 52 | 'nuxt-leaflet', 53 | 'nuxt-clipboard2', 54 | ], 55 | 56 | /* 57 | ** Axios module configuration 58 | ** See https://axios.nuxtjs.org/options 59 | */ 60 | axios: { 61 | baseUrl: process.env.BASE_URL || 'http://localhost:3030', 62 | browserBaseURL: process.env.BASE_URL || 'http://localhost:3030', 63 | }, 64 | /* 65 | ** monent.js Configurtion 66 | */ 67 | moment: { 68 | timezone: true, 69 | locales: ['ja'], 70 | }, 71 | /* 72 | ** Build configuration 73 | */ 74 | build: { 75 | /* 76 | ** You can extend webpack config here 77 | */ 78 | extend(config, ctx) {}, 79 | }, 80 | 81 | buildDir: 'dist', 82 | 83 | server: { 84 | host: '0.0.0.0', 85 | port: 3030, 86 | }, 87 | env: { 88 | baseUrl: process.env.BASE_URL || 'http://localhost:3030', 89 | }, 90 | serverMiddleware: ['~/api/index.js'], 91 | }; 92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sakepedia-nuxt", 3 | "version": "1.0.0", 4 | "description": "みんなで作る日本酒オープンデータ", 5 | "author": "nichesuch", 6 | "private": true, 7 | "scripts": { 8 | "dev": "cross-env NODE_ENV=development nodemon server/index.js --trace-deprecation --watch server", 9 | "build": "nuxt build", 10 | "start": "cross-env NODE_ENV=production node server/index.js", 11 | "generate": "nuxt generate", 12 | "test:jest": "jest --config jest.config.js", 13 | "lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore .", 14 | "lintfix": "eslint --fix --ext .ts,.js,.vue --ignore-path .gitignore .", 15 | "swagger-autogen": "node api/swagger.js" 16 | }, 17 | "dependencies": { 18 | "@geolonia/normalize-japanese-addresses": "^2.3.0", 19 | "@nuxtjs/auth": "^4.9.1", 20 | "@nuxtjs/axios": "^5.13.1", 21 | "@nuxtjs/style-resources": "^1.0.0", 22 | "@popperjs/core": "^2.9.2", 23 | "bcryptjs": "^2.4.3", 24 | "bootstrap": "^4.6.0", 25 | "bootstrap-vue": "^2.21.2", 26 | "cross-env": "^7.0.3", 27 | "express": "^4.17.1", 28 | "express-paginate": "^1.0.2", 29 | "express-session": "^1.17.1", 30 | "express-validator": "^6.10.0", 31 | "jquery": "^1.9.1", 32 | "jsonwebtoken": "^8.5.1", 33 | "mongoose": "^5.12.4", 34 | "mongoose-paginate": "^5.0.3", 35 | "node-sass": "^4.14.1", 36 | "nuxt": "^2.15.4", 37 | "nuxt-clipboard2": "^0.2.1", 38 | "nuxt-leaflet": "^0.0.25", 39 | "passport": "^0.4.1", 40 | "passport-github": "^1.1.0", 41 | "popper.js": "^1.16.1", 42 | "sass-loader": "10.1.1", 43 | "semantic-ui-css": "^2.4.1", 44 | "semantic-ui-vue": "^0.11.0", 45 | "swagger-autogen": "^2.22.0", 46 | "swagger-jsdoc": "^6.2.3", 47 | "swagger-ui-express": "^4.5.0", 48 | "vue": "^2.6.12", 49 | "vue-search-select": "^2.9.3", 50 | "vue-select": "^3.11.2", 51 | "vue2-leaflet-markercluster": "^3.1.0", 52 | "webpack": "^4.46.0" 53 | }, 54 | "devDependencies": { 55 | "@babel/core": "^7.13.15", 56 | "@babel/eslint-parser": "^7.13.14", 57 | "@nuxtjs/moment": "^1.6.1", 58 | "@vue/eslint-config-prettier": "^6.0.0", 59 | "@vue/test-utils": "^1.1.4", 60 | "babel-core": "^7.0.0-bridge.0", 61 | "babel-jest": "^26.6.3", 62 | "core-js": "3", 63 | "eslint": "^7.24.0", 64 | "eslint-config-prettier": "^8.2.0", 65 | "eslint-plugin-nuxt": "^2.0.0", 66 | "eslint-plugin-prettier": "^3.4.0", 67 | "eslint-plugin-vue": "^7.9.0", 68 | "eslint-webpack-plugin": "^2.5.3", 69 | "jest": "^26.6.3", 70 | "nodemon": "^2.0.7", 71 | "prettier": "^2.2.1", 72 | "vue-jest": "^3.0.7", 73 | "vue-template-compiler": "^2.6.12" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /pages/README.md: -------------------------------------------------------------------------------- 1 | # PAGES 2 | 3 | This directory contains your Application Views and Routes. 4 | The framework reads all the `*.vue` files inside this directory and creates the router of your application. 5 | 6 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing). 7 | -------------------------------------------------------------------------------- /pages/about/index.vue: -------------------------------------------------------------------------------- 1 | 38 | -------------------------------------------------------------------------------- /pages/auth/account.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 96 | -------------------------------------------------------------------------------- /pages/auth/login.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 38 | -------------------------------------------------------------------------------- /pages/auth/logout.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 25 | -------------------------------------------------------------------------------- /pages/brands/_id/index.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 130 | -------------------------------------------------------------------------------- /pages/brands/_id/update.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 131 | -------------------------------------------------------------------------------- /pages/brands/add.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 140 | -------------------------------------------------------------------------------- /pages/brands/index.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 134 | -------------------------------------------------------------------------------- /pages/breweries/index.vue: -------------------------------------------------------------------------------- 1 | 76 | 77 | 161 | -------------------------------------------------------------------------------- /pages/bydatas/_id/index.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 109 | -------------------------------------------------------------------------------- /pages/bydatas/index.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 135 | -------------------------------------------------------------------------------- /pages/callback.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 27 | -------------------------------------------------------------------------------- /pages/comments/_id/index.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 132 | 142 | -------------------------------------------------------------------------------- /pages/comments/_id/update.vue: -------------------------------------------------------------------------------- 1 | 94 | 95 | 154 | 160 | -------------------------------------------------------------------------------- /pages/comments/add.vue: -------------------------------------------------------------------------------- 1 | 88 | 89 | 180 | 186 | -------------------------------------------------------------------------------- /pages/comments/index.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 153 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 99 | 100 | 136 | 137 | 150 | -------------------------------------------------------------------------------- /pages/sakes/_id/index.vue: -------------------------------------------------------------------------------- 1 | 104 | 105 | 174 | -------------------------------------------------------------------------------- /pages/sakes/_id/update.vue: -------------------------------------------------------------------------------- 1 | 123 | 124 | 179 | -------------------------------------------------------------------------------- /pages/sakes/index.vue: -------------------------------------------------------------------------------- 1 | 80 | 81 | 160 | -------------------------------------------------------------------------------- /pages/user/contribute.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 103 | 104 | 109 | -------------------------------------------------------------------------------- /plugins/README.md: -------------------------------------------------------------------------------- 1 | # PLUGINS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains Javascript plugins that you want to run before mounting the root Vue.js application. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins). 8 | -------------------------------------------------------------------------------- /plugins/moment-filter.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import moment from 'moment'; 3 | 4 | Vue.filter('date', function (value) { 5 | if (!value) return undefined; 6 | const date = moment(value); 7 | return date.isValid() ? date.format('YYYY/MM/DD') : undefined; 8 | }); 9 | Vue.filter('datetime', function (value) { 10 | if (!value) return undefined; 11 | const date = moment(value); 12 | return date.isValid() ? date.format('YYYY/MM/DD HH:mm') : undefined; 13 | }); 14 | -------------------------------------------------------------------------------- /plugins/string-filter.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | Vue.filter('omitted', function (text) { 4 | return text != null && text.length > 10 ? text.slice(0, 10) + '…' : text; 5 | }); 6 | -------------------------------------------------------------------------------- /plugins/vue2-leaflet-markercluster.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import * as L from 'leaflet'; 3 | import Vue2LeafletMarkerCluster from 'vue2-leaflet-markercluster'; 4 | Vue.component('VMarkerCluster', Vue2LeafletMarkerCluster); 5 | 6 | const LeafletPlugin = { 7 | install(Vue) { 8 | // Expose Leaflet 9 | Vue.prototype.$L = L; 10 | }, 11 | }; 12 | 13 | Vue.use(LeafletPlugin); 14 | -------------------------------------------------------------------------------- /plugins/vueselect.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import vSelect from 'vue-select'; 3 | import 'vue-select/dist/vue-select.css'; 4 | 5 | Vue.component('VSelect', vSelect); 6 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const consola = require('consola'); 3 | const { Nuxt, Builder } = require('nuxt'); 4 | const app = express(); 5 | 6 | // Import and Set Nuxt.js options 7 | const config = require('../nuxt.config.js'); 8 | config.dev = process.env.NODE_ENV !== 'production'; 9 | 10 | async function start() { 11 | // Init Nuxt.js 12 | const nuxt = new Nuxt(config); 13 | 14 | const { host, port } = nuxt.options.server; 15 | 16 | // Build only in dev mode 17 | if (config.dev) { 18 | const builder = new Builder(nuxt); 19 | await builder.build(); 20 | } else { 21 | await nuxt.ready(); 22 | } 23 | 24 | // Give nuxt middleware to express 25 | app.use(nuxt.render); 26 | 27 | // Listen the server 28 | app.listen(port, host); 29 | consola.ready({ 30 | message: `Server listening on http://${host}:${port}`, 31 | badge: true, 32 | }); 33 | } 34 | start(); 35 | -------------------------------------------------------------------------------- /static/README.md: -------------------------------------------------------------------------------- 1 | # STATIC 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your static files. 6 | Each file inside this directory is mapped to `/`. 7 | Thus you'd want to delete this README.md before deploying to production. 8 | 9 | Example: `/static/robots.txt` is mapped as `/robots.txt`. 10 | 11 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static). 12 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Code-for-SAKE/Sakepedia-Nuxt/7b9871792990c4c80f5e50ccc63cfa0c8343ab8e/static/favicon.ico -------------------------------------------------------------------------------- /store/README.md: -------------------------------------------------------------------------------- 1 | # STORE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Vuex Store files. 6 | Vuex Store option is implemented in the Nuxt.js framework. 7 | 8 | Creating a file in this directory automatically activates the option in the framework. 9 | 10 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store). 11 | -------------------------------------------------------------------------------- /store/flash.js: -------------------------------------------------------------------------------- 1 | export const state = () => ({ 2 | text: '', 3 | mode: 'processing', 4 | visible: false, 5 | timeoutId: -1, 6 | defaultDuration: 10000, 7 | }); 8 | 9 | export const mutations = { 10 | setMessage: (state, payload) => { 11 | state.text = payload.text; 12 | state.mode = payload.mode; 13 | state.visible = true; 14 | }, 15 | setMessageVisible: (state, value) => (state.visible = value), 16 | setMessageTimeoutId: (state, value) => (state.timeoutId = value), 17 | clearMessageTimeoutId: (state) => (state.timeoutId = -1), 18 | }; 19 | 20 | export const actions = { 21 | show: ({ state, commit }, message) => 22 | new Promise((resolve, reject) => { 23 | //timeoutId !== 1 のときはVisibleを変更するsetTimeoutが生きているのでキャンセルする 24 | if (state.timeoutId !== -1) { 25 | clearTimeout(state.timeoutId); 26 | commit('clearMessageTimeoutId'); 27 | } 28 | 29 | commit('setMessage', message); 30 | 31 | if (!message.duration) message.duration = state.defaultDuration; 32 | 33 | if (message.duration > 0) { 34 | //durationだけ時間が経ったらVisible=falseとする(メッセージを隠す) 35 | const timeoutId = setTimeout(() => { 36 | commit('clearMessageTimeoutId'); 37 | commit('setMessageVisible', false); 38 | return resolve(); 39 | }, message.duration); 40 | commit('setMessageTimeoutId', timeoutId); 41 | } else { 42 | return resolve(); 43 | } 44 | }), 45 | }; 46 | -------------------------------------------------------------------------------- /store/index.js: -------------------------------------------------------------------------------- 1 | export const state = () => ({ 2 | user: null, 3 | auth: false, 4 | }); 5 | 6 | export const mutations = { 7 | login(state, payload) { 8 | state.auth = true; 9 | state.user = payload; 10 | }, 11 | logout(state) { 12 | state.auth = false; 13 | state.user = null; 14 | }, 15 | }; 16 | 17 | export const actions = { 18 | nuxtServerInit({ dispatch }, { req }) { 19 | return dispatch('getSession'); 20 | }, 21 | async getSession({ commit }) { 22 | const session = await this.$axios.$get('/api/session'); 23 | if (session && session.user) { 24 | commit('login', session.user); 25 | } 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /tests/components/BrandList.test.js: -------------------------------------------------------------------------------- 1 | import { mount, RouterLinkStub } from '@vue/test-utils'; 2 | import BrandList from '@/components/BrandList.vue'; 3 | import { getList } from '../../lib/ApiClient/getList'; 4 | 5 | jest.mock('../../lib/ApiClient/getList', () => ({ 6 | __esModule: true, 7 | getList: jest.fn(() => 8 | Promise.resolve({ 9 | list: [ 10 | { _id: 'test', name: 'test' }, 11 | { _id: 'test2', name: 'test2' }, 12 | { _id: 'test3', name: 'test3' }, 13 | ], 14 | currentPage: 1, 15 | count: 0, 16 | }) 17 | ), 18 | })); 19 | 20 | describe('components/BrandList.vue', () => { 21 | let wrapper; 22 | let responseMock; 23 | 24 | beforeEach(() => { 25 | wrapper = mount(BrandList, { 26 | propsData: { 27 | search: {}, 28 | }, 29 | stubs: { 30 | 'nuxt-link': RouterLinkStub, 31 | 'b-pagination': true, 32 | }, 33 | }); 34 | }); 35 | it('is a Vue instance', () => { 36 | expect(wrapper.vm).toBeTruthy(); 37 | }); 38 | describe('template', () => { 39 | describe('renderd correctly', () => { 40 | describe('with data', () => { 41 | beforeAll(() => { 42 | responseMock = { 43 | list: [ 44 | { _id: 'test', name: 'test' }, 45 | { _id: 'test2', name: 'test2' }, 46 | { _id: 'test3', name: 'test3' }, 47 | ], 48 | currentPage: 1, 49 | count: 0, 50 | }; 51 | getList.mockImplementation(() => { 52 | return Promise.resolve(responseMock); 53 | }); 54 | }); 55 | it('will be renderd correctly', () => { 56 | expect(wrapper.html()).toMatchSnapshot(); 57 | }); 58 | }); 59 | 60 | describe('without data', () => { 61 | beforeAll(() => { 62 | responseMock = { 63 | list: [], 64 | currentPage: 1, 65 | count: 0, 66 | }; 67 | getList.mockImplementation(() => { 68 | return Promise.resolve(responseMock); 69 | }); 70 | }); 71 | it('will be renderd correctly', () => { 72 | expect(wrapper.html()).toMatchSnapshot(); 73 | }); 74 | }); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /tests/components/BreweryYearDataList.test.js: -------------------------------------------------------------------------------- 1 | import { mount, RouterLinkStub } from '@vue/test-utils'; 2 | import BreweryYearDataList from '@/components/BreweryYearDataList.vue'; 3 | import { getList } from '../../lib/ApiClient/getList'; 4 | 5 | jest.mock('../../lib/ApiClient/getList', () => ({ 6 | __esModule: true, 7 | getList: jest.fn(() => 8 | Promise.resolve({ 9 | list: [ 10 | { _id: 'test', name: 'test', sake: { name: 'test1' } }, 11 | { _id: 'test2', name: 'test2', sake: { name: 'test2' } }, 12 | { _id: 'test3', name: 'test3', sake: { name: 'test3' } }, 13 | ], 14 | currentPage: 1, 15 | count: 0, 16 | }) 17 | ), 18 | })); 19 | 20 | describe('components/BreweryYearDataList.vue', () => { 21 | let wrapper; 22 | let responseMock; 23 | 24 | beforeEach(() => { 25 | wrapper = mount(BreweryYearDataList, { 26 | propsData: { 27 | search: {}, 28 | }, 29 | stubs: { 30 | 'nuxt-link': RouterLinkStub, 31 | 'b-pagination': true, 32 | }, 33 | }); 34 | }); 35 | it('is a Vue instance', () => { 36 | expect(wrapper.vm).toBeTruthy(); 37 | }); 38 | describe('template', () => { 39 | describe('renderd correctly', () => { 40 | describe('with data', () => { 41 | beforeAll(() => { 42 | responseMock = { 43 | list: [ 44 | { _id: 'test', name: 'test', sake: { name: 'test1' } }, 45 | { _id: 'test2', name: 'test2', sake: { name: 'test2' } }, 46 | { _id: 'test3', name: 'test3', sake: { name: 'test3' } }, 47 | ], 48 | currentPage: 1, 49 | count: 0, 50 | }; 51 | getList.mockImplementation(() => { 52 | return Promise.resolve(responseMock); 53 | }); 54 | }); 55 | it('will be renderd correctly', () => { 56 | expect(wrapper.html()).toMatchSnapshot(); 57 | }); 58 | }); 59 | 60 | describe('without data', () => { 61 | beforeAll(() => { 62 | responseMock = { 63 | list: [], 64 | currentPage: 1, 65 | count: 0, 66 | }; 67 | getList.mockImplementation(() => { 68 | return Promise.resolve(responseMock); 69 | }); 70 | }); 71 | it('will be renderd correctly', () => { 72 | expect(wrapper.html()).toMatchSnapshot(); 73 | }); 74 | }); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /tests/components/SakeList.test.js: -------------------------------------------------------------------------------- 1 | import { mount, RouterLinkStub } from '@vue/test-utils'; 2 | import SakeList from '@/components/SakeList.vue'; 3 | import { getList } from '../../lib/ApiClient/getList'; 4 | 5 | jest.mock('../../lib/ApiClient/getList', () => ({ 6 | __esModule: true, 7 | getList: jest.fn(() => 8 | Promise.resolve({ 9 | list: [ 10 | { _id: 'test', name: 'test' }, 11 | { _id: 'test2', name: 'test2' }, 12 | { _id: 'test3', name: 'test3' }, 13 | ], 14 | currentPage: 1, 15 | count: 0, 16 | }) 17 | ), 18 | })); 19 | 20 | describe('components/SakeList.vue', () => { 21 | let wrapper; 22 | let responseMock; 23 | 24 | beforeEach(() => { 25 | wrapper = mount(SakeList, { 26 | propsData: { 27 | search: {}, 28 | }, 29 | stubs: { 30 | 'nuxt-link': RouterLinkStub, 31 | 'b-pagination': true, 32 | }, 33 | }); 34 | }); 35 | it('is a Vue instance', () => { 36 | expect(wrapper.vm).toBeTruthy(); 37 | }); 38 | describe('template', () => { 39 | describe('renderd correctly', () => { 40 | describe('with data', () => { 41 | beforeAll(() => { 42 | responseMock = { 43 | list: [ 44 | { _id: 'test', name: 'test' }, 45 | { _id: 'test2', name: 'test2' }, 46 | { _id: 'test3', name: 'test3' }, 47 | ], 48 | currentPage: 1, 49 | count: 0, 50 | }; 51 | getList.mockImplementation(() => { 52 | return Promise.resolve(responseMock); 53 | }); 54 | }); 55 | it('will be renderd correctly', () => { 56 | expect(wrapper.html()).toMatchSnapshot(); 57 | }); 58 | }); 59 | 60 | describe('without data', () => { 61 | beforeAll(() => { 62 | responseMock = { 63 | list: [], 64 | currentPage: 1, 65 | count: 0, 66 | }; 67 | getList.mockImplementation(() => { 68 | return Promise.resolve(responseMock); 69 | }); 70 | }); 71 | it('will be renderd correctly', () => { 72 | expect(wrapper.html()).toMatchSnapshot(); 73 | }); 74 | }); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /tests/components/__snapshots__/BrandList.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`components/BrandList.vue template renderd correctly with data will be renderd correctly 1`] = ` 4 | "
5 | 6 | 13 |
" 14 | `; 15 | 16 | exports[`components/BrandList.vue template renderd correctly without data will be renderd correctly 1`] = ` 17 | "
18 | 19 |
データがありません
20 |
" 21 | `; 22 | -------------------------------------------------------------------------------- /tests/components/__snapshots__/BreweryYearDataList.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`components/BreweryYearDataList.vue template renderd correctly with data will be renderd correctly 1`] = ` 4 | "
5 | 6 | 16 |
" 17 | `; 18 | 19 | exports[`components/BreweryYearDataList.vue template renderd correctly without data will be renderd correctly 1`] = ` 20 | "
21 | 22 |
データがありません
23 |
" 24 | `; 25 | -------------------------------------------------------------------------------- /tests/components/__snapshots__/SakeList.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`components/SakeList.vue template renderd correctly with data will be renderd correctly 1`] = ` 4 | "
5 | 6 | 13 |
" 14 | `; 15 | 16 | exports[`components/SakeList.vue template renderd correctly without data will be renderd correctly 1`] = ` 17 | "
18 | 19 |
データがありません
20 |
" 21 | `; 22 | -------------------------------------------------------------------------------- /tests/lib/ApiClient/getList.test.js: -------------------------------------------------------------------------------- 1 | import { getList } from '../../../lib/ApiClient/getList'; 2 | import axios from 'axios'; 3 | 4 | jest.mock('axios'); 5 | 6 | describe('lib/ApiClient/getList.js', () => { 7 | axios.get.mockImplementation(() => { 8 | return Promise.resolve({ 9 | data: { 10 | sakes: [], 11 | currentPage: 1, 12 | pageCount: 1, 13 | }, 14 | }); 15 | }); 16 | describe('with correct arguements', () => { 17 | const type = 'sakes'; 18 | const queryParams = { 19 | page: 1, 20 | limit: 10, 21 | }; 22 | it('axios get has been called with query params', async () => { 23 | await getList(type, queryParams); 24 | expect(axios.get).toHaveBeenCalledWith(`/api/${type}`, { 25 | params: queryParams, 26 | }); 27 | }); 28 | it('getList return correct value', async () => { 29 | const res = await getList(type, queryParams); 30 | expect(res).toEqual({ 31 | list: [], 32 | currentPage: 1, 33 | count: 1, 34 | }); 35 | }); 36 | }); 37 | describe('with empty properties', () => { 38 | const type = 'sakes'; 39 | const queryParams = { 40 | page: '', 41 | limit: '', 42 | }; 43 | it('the properties have been removed', async () => { 44 | await getList(type, queryParams); 45 | expect(axios.get).toHaveBeenCalledWith(`/api/${type}`, { params: {} }); 46 | }); 47 | }); 48 | describe('with searchName', () => { 49 | const type = 'sakes'; 50 | const queryParams = { 51 | searchName: 'test', 52 | }; 53 | it('searchName copied to keyword', async () => { 54 | await getList(type, queryParams); 55 | expect(axios.get).toHaveBeenCalledWith(`/api/${type}`, { 56 | params: { keyword: 'test' }, 57 | }); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /tests/pages/brands/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`pages/brands/index.vue template renderd correctly with data will be renderd correctly 1`] = ` 4 | "
5 |
6 |

銘柄一覧

7 | 追加 8 |
9 |
10 |
11 |
12 |
13 | 検索 14 |
15 |
16 |
17 | 18 |
19 | 26 |
" 27 | `; 28 | 29 | exports[`pages/brands/index.vue template renderd correctly without data will be renderd correctly 1`] = ` 30 | "
31 |
32 |

銘柄一覧

33 | 追加 34 |
35 |
36 |
37 |
38 |
39 | 検索 40 |
41 |
42 |
43 | 44 |
45 |
データがありません
46 |
" 47 | `; 48 | -------------------------------------------------------------------------------- /tests/pages/brands/index.test.js: -------------------------------------------------------------------------------- 1 | import { mount, RouterLinkStub } from '@vue/test-utils'; 2 | import index from '@/pages/brands/index.vue'; 3 | import { getList } from '../../../lib/ApiClient/getList'; 4 | 5 | jest.mock('../../../lib/ApiClient/getList', () => ({ 6 | __esModule: true, 7 | getList: jest.fn(() => 8 | Promise.resolve({ 9 | list: [ 10 | { _id: 'test', name: 'test' }, 11 | { _id: 'test2', name: 'test2' }, 12 | { _id: 'test3', name: 'test3' }, 13 | ], 14 | currentPage: 1, 15 | count: 0, 16 | }) 17 | ), 18 | })); 19 | 20 | describe('pages/brands/index.vue', () => { 21 | let wrapper; 22 | let responseMock; 23 | 24 | beforeEach(async () => { 25 | wrapper = mount(index, { 26 | propsData: { 27 | search: {}, 28 | }, 29 | stubs: { 30 | 'nuxt-link': RouterLinkStub, 31 | 'b-pagination': true, 32 | 'b-button': true, 33 | }, 34 | }); 35 | const context = { query: { name: '', page: '', limit: '' } }; 36 | const data = await wrapper.vm.$options.asyncData(context); 37 | wrapper.setData(data); 38 | }); 39 | it('is a Vue instance', () => { 40 | expect(wrapper.vm).toBeTruthy(); 41 | }); 42 | describe('template', () => { 43 | describe('renderd correctly', () => { 44 | describe('with data', () => { 45 | beforeAll(() => { 46 | responseMock = { 47 | list: [ 48 | { _id: 'test', name: 'test' }, 49 | { _id: 'test2', name: 'test2' }, 50 | { _id: 'test3', name: 'test3' }, 51 | ], 52 | currentPage: 1, 53 | count: 0, 54 | }; 55 | getList.mockImplementation(() => { 56 | return Promise.resolve(responseMock); 57 | }); 58 | }); 59 | it('will be renderd correctly', () => { 60 | expect(wrapper.html()).toMatchSnapshot(); 61 | }); 62 | }); 63 | 64 | describe('without data', () => { 65 | beforeAll(() => { 66 | responseMock = { 67 | list: [], 68 | currentPage: 1, 69 | count: 0, 70 | }; 71 | getList.mockImplementation(() => { 72 | return Promise.resolve(responseMock); 73 | }); 74 | }); 75 | it('will be renderd correctly', () => { 76 | expect(wrapper.html()).toMatchSnapshot(); 77 | }); 78 | }); 79 | }); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /tests/pages/breweries/index.test.js: -------------------------------------------------------------------------------- 1 | import { mount, RouterLinkStub } from '@vue/test-utils'; 2 | import index from '@/pages/breweries/index.vue'; 3 | import { getList } from '../../../lib/ApiClient/getList'; 4 | 5 | jest.mock('../../../lib/ApiClient/getList', () => ({ 6 | __esModule: true, 7 | getList: jest.fn(() => 8 | Promise.resolve({ 9 | list: [ 10 | { _id: 'test', name: 'test' }, 11 | { _id: 'test2', name: 'test2' }, 12 | { _id: 'test3', name: 'test3' }, 13 | ], 14 | currentPage: 1, 15 | count: 0, 16 | }) 17 | ), 18 | })); 19 | 20 | describe('pages/breweries/index.vue', () => { 21 | let wrapper; 22 | let responseMock; 23 | 24 | beforeEach(async () => { 25 | wrapper = mount(index, { 26 | propsData: { 27 | search: {}, 28 | }, 29 | stubs: { 30 | 'nuxt-link': RouterLinkStub, 31 | 'b-pagination': true, 32 | 'b-button': true, 33 | }, 34 | }); 35 | const context = { query: { name: '', page: '', limit: '' } }; 36 | const data = await wrapper.vm.$options.asyncData(context); 37 | wrapper.setData(data); 38 | }); 39 | it('is a Vue instance', () => { 40 | expect(wrapper.vm).toBeTruthy(); 41 | }); 42 | describe('template', () => { 43 | describe('renderd correctly', () => { 44 | describe('with data', () => { 45 | beforeAll(() => { 46 | responseMock = { 47 | list: [ 48 | { _id: 'test', name: 'test' }, 49 | { _id: 'test2', name: 'test2' }, 50 | { _id: 'test3', name: 'test3' }, 51 | ], 52 | currentPage: 1, 53 | count: 0, 54 | }; 55 | getList.mockImplementation(() => { 56 | return Promise.resolve(responseMock); 57 | }); 58 | }); 59 | it('will be renderd correctly', () => { 60 | expect(wrapper.html()).toMatchSnapshot(); 61 | }); 62 | }); 63 | 64 | describe('without data', () => { 65 | beforeAll(() => { 66 | responseMock = { 67 | list: [], 68 | currentPage: 1, 69 | count: 0, 70 | }; 71 | getList.mockImplementation(() => { 72 | return Promise.resolve(responseMock); 73 | }); 74 | }); 75 | it('will be renderd correctly', () => { 76 | expect(wrapper.html()).toMatchSnapshot(); 77 | }); 78 | }); 79 | }); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /tests/pages/sakes/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`pages/sakes/index.vue template renderd correctly with data will be renderd correctly 1`] = ` 4 | "
5 |
6 |

日本酒一覧

7 | 追加 8 |
9 |
10 |
11 |
12 |
13 | 検索 14 |
15 |
16 | タグで検索 17 |
18 |
19 |
20 | 21 |
22 | 29 |
" 30 | `; 31 | 32 | exports[`pages/sakes/index.vue template renderd correctly without data will be renderd correctly 1`] = ` 33 | "
34 |
35 |

日本酒一覧

36 | 追加 37 |
38 |
39 |
40 |
41 |
42 | 検索 43 |
44 |
45 | タグで検索 46 |
47 |
48 |
49 | 50 |
51 |
データがありません
52 |
" 53 | `; 54 | -------------------------------------------------------------------------------- /tests/pages/sakes/index.test.js: -------------------------------------------------------------------------------- 1 | import { mount, RouterLinkStub } from '@vue/test-utils'; 2 | import index from '@/pages/sakes/index.vue'; 3 | import { getList } from '../../../lib/ApiClient/getList'; 4 | 5 | jest.mock('../../../lib/ApiClient/getList', () => ({ 6 | __esModule: true, 7 | getList: jest.fn(() => 8 | Promise.resolve({ 9 | list: [ 10 | { _id: 'test', name: 'test' }, 11 | { _id: 'test2', name: 'test2' }, 12 | { _id: 'test3', name: 'test3' }, 13 | ], 14 | currentPage: 1, 15 | count: 0, 16 | }) 17 | ), 18 | })); 19 | 20 | describe('pages/sakes/index.vue', () => { 21 | let wrapper; 22 | let responseMock; 23 | 24 | beforeEach(async () => { 25 | const $route = { 26 | query: { 27 | type: 'test', 28 | }, 29 | }; 30 | wrapper = mount(index, { 31 | mocks: { 32 | $route, 33 | }, 34 | propsData: { 35 | search: {}, 36 | }, 37 | stubs: { 38 | 'nuxt-link': RouterLinkStub, 39 | 'b-pagination': true, 40 | 'b-button': true, 41 | }, 42 | }); 43 | const context = { query: { name: '', page: '', limit: '' } }; 44 | const data = await wrapper.vm.$options.asyncData(context); 45 | wrapper.setData(data); 46 | }); 47 | it('is a Vue instance', () => { 48 | expect(wrapper.vm).toBeTruthy(); 49 | }); 50 | describe('template', () => { 51 | describe('renderd correctly', () => { 52 | describe('with data', () => { 53 | beforeAll(() => { 54 | responseMock = { 55 | list: [ 56 | { _id: 'test', name: 'test' }, 57 | { _id: 'test2', name: 'test2' }, 58 | { _id: 'test3', name: 'test3' }, 59 | ], 60 | currentPage: 1, 61 | count: 0, 62 | }; 63 | getList.mockImplementation(() => { 64 | return Promise.resolve(responseMock); 65 | }); 66 | }); 67 | it('will be renderd correctly', () => { 68 | expect(wrapper.html()).toMatchSnapshot(); 69 | }); 70 | }); 71 | 72 | describe('without data', () => { 73 | beforeAll(() => { 74 | responseMock = { 75 | list: [], 76 | currentPage: 1, 77 | count: 0, 78 | }; 79 | getList.mockImplementation(() => { 80 | return Promise.resolve(responseMock); 81 | }); 82 | }); 83 | it('will be renderd correctly', () => { 84 | expect(wrapper.html()).toMatchSnapshot(); 85 | }); 86 | }); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /utils/japanese.js: -------------------------------------------------------------------------------- 1 | //ひらがな、カタカナ変換 2 | module.exports = { 3 | // カタカナがあればひらがなに変換 4 | kanaToHira(str) { 5 | return str.replace(/[\u30a1-\u30f6]/g, function (match) { 6 | var chr = match.charCodeAt(0) - 0x60; 7 | return String.fromCharCode(chr); 8 | }); 9 | }, 10 | // ひらがながあればカタカナに変換 11 | hiraToKana(str) { 12 | return str.replace(/[\u3041-\u3096]/g, function (match) { 13 | var chr = match.charCodeAt(0) + 0x60; 14 | return String.fromCharCode(chr); 15 | }); 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /utils/prefectures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | prefectures: [ 3 | null, 4 | '北海道', 5 | '青森県', 6 | '岩手県', 7 | '宮城県', 8 | '秋田県', 9 | '山形県', 10 | '福島県', 11 | '茨城県', 12 | '栃木県', 13 | '群馬県', 14 | '埼玉県', 15 | '千葉県', 16 | '東京都', 17 | '神奈川県', 18 | '新潟県', 19 | '富山県', 20 | '石川県', 21 | '福井県', 22 | '山梨県', 23 | '長野県', 24 | '岐阜県', 25 | '静岡県', 26 | '愛知県', 27 | '三重県', 28 | '滋賀県', 29 | '京都府', 30 | '大阪府', 31 | '兵庫県', 32 | '奈良県', 33 | '和歌山県', 34 | '鳥取県', 35 | '島根県', 36 | '岡山県', 37 | '広島県', 38 | '山口県', 39 | '徳島県', 40 | '香川県', 41 | '愛媛県', 42 | '高知県', 43 | '福岡県', 44 | '佐賀県', 45 | '長崎県', 46 | '熊本県', 47 | '大分県', 48 | '宮崎県', 49 | '鹿児島県', 50 | '沖縄県', 51 | ], 52 | }; 53 | --------------------------------------------------------------------------------