├── server ├── errcheck_excludes.txt ├── interface │ └── controller │ │ ├── request.go │ │ ├── response.go │ │ ├── error_code.go │ │ ├── validator.go │ │ ├── dto.go │ │ ├── authentication.go │ │ ├── errors.go │ │ ├── thread.go │ │ └── comment.go ├── util │ ├── uuid.go │ └── crypt.go ├── infra │ ├── router │ │ └── router.go │ ├── db │ │ ├── common.go │ │ ├── tx_closer.go │ │ ├── query │ │ │ └── db_manager.go │ │ ├── db_manager.go │ │ └── session.go │ └── logger │ │ └── logger.go ├── testutil │ ├── core_helper.go │ ├── time_helper.go │ └── model_helper.go ├── domain │ ├── model │ │ ├── session.go │ │ ├── user.go │ │ ├── thread.go │ │ ├── comment.go │ │ ├── const.go │ │ └── errrors.go │ ├── repository │ │ ├── session.go │ │ ├── user.go │ │ ├── comment.go │ │ ├── thread.go │ │ └── mock │ │ │ ├── session.go │ │ │ ├── user.go │ │ │ ├── comment.go │ │ │ └── thread.go │ └── service │ │ ├── authentication.go │ │ ├── comment.go │ │ ├── mock │ │ ├── comment.go │ │ ├── authentication.go │ │ ├── thread.go │ │ ├── session.go │ │ └── user.go │ │ ├── session.go │ │ ├── thread.go │ │ ├── user.go │ │ ├── session_test.go │ │ ├── comment_test.go │ │ ├── authentication_test.go │ │ ├── user_test.go │ │ └── thread_test.go ├── application │ ├── tx.go │ ├── mock │ │ ├── authentication.go │ │ ├── thread.go │ │ └── comment.go │ ├── comment.go │ └── thread.go ├── Gopkg.toml ├── Makefile ├── middleware │ └── authentication.go ├── main.go └── Gopkg.lock ├── client └── nuxt-vue-go-chat │ ├── .prettierrc │ ├── app │ ├── assets │ │ ├── style │ │ │ ├── variables.styl │ │ │ └── app.styl │ │ └── README.md │ ├── plugins │ │ ├── vuelidate.js │ │ ├── moment.js │ │ ├── axios.js │ │ ├── README.md │ │ └── vuetify.js │ ├── static │ │ ├── v.png │ │ ├── icon.png │ │ ├── favicon.ico │ │ └── README.md │ ├── components │ │ ├── README.md │ │ ├── Footer.vue │ │ ├── Container.vue │ │ ├── Toolbar.vue │ │ ├── ThreadsInput.vue │ │ ├── CommentInput.vue │ │ ├── Login.vue │ │ └── SignUp.vue │ ├── test │ │ └── Logo.spec.js │ ├── layouts │ │ ├── README.md │ │ └── default.vue │ ├── pages │ │ ├── README.md │ │ ├── index.vue │ │ └── threads │ │ │ ├── index.vue │ │ │ └── _id │ │ │ └── comments │ │ │ └── index.vue │ ├── middleware │ │ └── README.md │ └── store │ │ ├── README.md │ │ ├── action-types.js │ │ ├── mutation-types.js │ │ ├── index.js │ │ ├── threads.js │ │ └── comments.js │ ├── .editorconfig │ ├── .babelrc │ ├── jest.config.js │ ├── .eslintrc.js │ ├── README.md │ ├── .gitignore │ ├── package.json │ └── nuxt.config.js ├── mysql ├── my.cnf └── init │ └── setup.sql ├── docker └── Dockerfile ├── .travis.yml ├── docker-compose.yml ├── .gitignore └── README.md /server/errcheck_excludes.txt: -------------------------------------------------------------------------------- 1 | (*database/sql.DB).Close 2 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /server/interface/controller/request.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | const ( 4 | defaultLimit = 20 5 | defaultCursor = 1 6 | ) 7 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/assets/style/variables.styl: -------------------------------------------------------------------------------- 1 | @require '../../../node_modules/vuetify/src/stylus/settings/_variables.styl' 2 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/plugins/vuelidate.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuelidate from 'vuelidate' 3 | Vue.use(Vuelidate) 4 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/static/v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekky0905/nuxt-vue-go-chat/HEAD/client/nuxt-vue-go-chat/app/static/v.png -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/assets/style/app.styl: -------------------------------------------------------------------------------- 1 | // Import Vuetify styling 2 | @require '../../../node_modules/vuetify/src/stylus/app.styl' 3 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekky0905/nuxt-vue-go-chat/HEAD/client/nuxt-vue-go-chat/app/static/icon.png -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekky0905/nuxt-vue-go-chat/HEAD/client/nuxt-vue-go-chat/app/static/favicon.ico -------------------------------------------------------------------------------- /mysql/my.cnf: -------------------------------------------------------------------------------- 1 | [mysqld] 2 | character-set-server=utf8mb4 3 | collation-server=utf8mb4_general_ci 4 | 5 | 6 | [client] 7 | default-character-set=utf8mb4 8 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/plugins/moment.js: -------------------------------------------------------------------------------- 1 | import 'moment/locale/ja' 2 | import moment from 'moment' 3 | 4 | moment.locale('ja') 5 | export default moment 6 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.11 2 | 3 | ADD ./ /go/src/github.com/sekky0905/nuxt-vue-go-chat 4 | 5 | WORKDIR /go/src/github.com/sekky0905/nuxt-vue-go-chat 6 | -------------------------------------------------------------------------------- /server/util/uuid.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "github.com/google/uuid" 4 | 5 | // UUID generates UUID. 6 | func UUID() string { 7 | return uuid.New().String() 8 | } 9 | -------------------------------------------------------------------------------- /server/infra/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | // G is the gin engine. 8 | var G *gin.Engine 9 | 10 | func init() { 11 | g := gin.New() 12 | G = g 13 | } 14 | -------------------------------------------------------------------------------- /server/testutil/core_helper.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import "testing" 4 | 5 | // Errorf notify error. 6 | func Errorf(tb testing.TB, want, got interface{}) { 7 | tb.Helper() 8 | tb.Errorf("want = %#v, got = %#v", want, got) 9 | } 10 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/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 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/.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 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/components/Footer.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/test/Logo.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import Logo from '@/components/Logo.vue' 3 | 4 | describe('Logo', () => { 5 | test('is a Vue instance', () => { 6 | const wrapper = mount(Logo) 7 | expect(wrapper.isVueInstance()).toBeTruthy() 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.11.x" 5 | 6 | cache: 7 | directories: 8 | - vendor 9 | 10 | before_install: 11 | - go get -u github.com/golang/dep/cmd/dep 12 | 13 | install: 14 | - cd server && make tools 15 | - dep ensure 16 | 17 | script: 18 | - make check 19 | - make test 20 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/.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 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/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 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/plugins/axios.js: -------------------------------------------------------------------------------- 1 | export default ({ app, $axios, redirect }) => { 2 | $axios.onError(error => { 3 | if (error.response.status === 401) { 4 | console.error(`failed to authenticate: ${JSON.stringify(error)}`) 5 | redirect('/') 6 | } 7 | return Promise.reject(error) 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/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 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/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 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/components/Container.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/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 | -------------------------------------------------------------------------------- /server/infra/db/common.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | const checkNum = 1 4 | 5 | // readyLimitForHasNext sets limit for hasNext. 6 | func readyLimitForHasNext(limit int) int { 7 | return limit + checkNum // DBで次が存在するかを確認するために、limitで指定された数に+1を行う 8 | } 9 | 10 | // checkHasNext check whether the data has already existed or not. 11 | func checkHasNext(length, limit int) bool { 12 | return length >= limit+checkNum 13 | } 14 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/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 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/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 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuetify from 'vuetify/lib' 3 | import colors from 'vuetify/es5/util/colors' 4 | 5 | Vue.use(Vuetify, { 6 | theme: { 7 | primary: colors.blue.darken2, 8 | accent: colors.grey.darken3, 9 | secondary: colors.amber.darken3, 10 | info: colors.teal.lighten1, 11 | warning: colors.amber.base, 12 | error: colors.deepOrange.accent4, 13 | success: colors.green.accent3 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleNameMapper: { 3 | '^@/(.*)$': '/$1', 4 | '^~/(.*)$': '/$1', 5 | '^vue$': 'vue/dist/vue.common.js' 6 | }, 7 | moduleFileExtensions: ['js', 'vue', 'json'], 8 | transform: { 9 | '^.+\\.js$': 'babel-jest', 10 | '.*\\.(vue)$': 'vue-jest' 11 | }, 12 | collectCoverage: true, 13 | collectCoverageFrom: [ 14 | '/components/**/*.vue', 15 | '/pages/**/*.vue' 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true 6 | }, 7 | parserOptions: { 8 | parser: 'babel-eslint' 9 | }, 10 | extends: [ 11 | '@nuxtjs', 12 | 'plugin:nuxt/recommended', 13 | 'plugin:prettier/recommended', 14 | 'prettier', 15 | 'prettier/vue' 16 | ], 17 | plugins: [ 18 | 'prettier' 19 | ], 20 | // add your custom rules here 21 | rules: { 22 | "no-console": "off", 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/README.md: -------------------------------------------------------------------------------- 1 | # nuxt-vue-go-chat 2 | 3 | > My impressive Nuxt.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | $ yarn install 10 | 11 | # serve with hot reload at localhost:3000 12 | $ yarn run dev 13 | 14 | # build for production and launch server 15 | $ yarn run build 16 | $ yarn start 17 | 18 | # generate static project 19 | $ yarn run generate 20 | ``` 21 | 22 | For detailed explanation on how things work, checkout [Nuxt.js docs](https://nuxtjs.org). 23 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/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 | -------------------------------------------------------------------------------- /server/domain/model/session.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "go.uber.org/zap/zapcore" 7 | ) 8 | 9 | // Session is Session model. 10 | type Session struct { 11 | ID string 12 | UserID uint32 13 | CreatedAt time.Time 14 | } 15 | 16 | // MarshalLogObject for zap logger. 17 | func (s Session) MarshalLogObject(enc zapcore.ObjectEncoder) error { 18 | enc.AddString("id", s.ID) 19 | enc.AddInt32("userID", int32(s.UserID)) 20 | enc.AddTime("createdAt", s.CreatedAt) 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 14 | 24 | -------------------------------------------------------------------------------- /server/domain/repository/session.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 7 | 8 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 9 | ) 10 | 11 | // SessionRepository is repository of session. 12 | type SessionRepository interface { 13 | GetSessionByID(ctx context.Context, m query.SQLManager, id string) (*model.Session, error) 14 | InsertSession(ctx context.Context, m query.SQLManager, session *model.Session) error 15 | DeleteSession(ctx context.Context, m query.SQLManager, id string) error 16 | } 17 | -------------------------------------------------------------------------------- /server/testutil/time_helper.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // reference 8 | // https://medium.com/@timakin/go-api-testing-173b97fb23ec 9 | 10 | // FakeTime is mock of time. 11 | var FakeTime time.Time 12 | 13 | // SetFakeTime sets FakeTime. 14 | func SetFakeTime(t time.Time) { 15 | FakeTime = t 16 | } 17 | 18 | // ResetFakeTime resets FakeTime. 19 | func ResetFakeTime() { 20 | FakeTime = time.Time{} 21 | } 22 | 23 | // TimeNow returns time now. 24 | func TimeNow() time.Time { 25 | if !FakeTime.IsZero() { 26 | return FakeTime 27 | } 28 | return time.Now() 29 | } 30 | -------------------------------------------------------------------------------- /server/application/tx.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 6 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 7 | ) 8 | 9 | // CloseTransaction executes after process of tx. 10 | type CloseTransaction func(tx query.TxManager, err error) error 11 | 12 | // beginTxErrorMsg generates and returns tx begin error message. 13 | func beginTxErrorMsg(err error) error { 14 | return errors.WithStack(&model.SQLError{ 15 | BaseErr: err, 16 | InvalidReasonForDeveloper: model.FailedToBeginTx, 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /server/interface/controller/response.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/logger" 6 | "go.uber.org/zap" 7 | ) 8 | 9 | // ResponseAndLogError returns response and log error. 10 | func ResponseAndLogError(g *gin.Context, err error) { 11 | he := handleError(err) 12 | 13 | errMsgField := zap.String("error message", he.Message) 14 | 15 | if he.BaseError != nil { 16 | logger.Logger.Error("", errMsgField, zap.String("base error", he.BaseError.Error())) 17 | } else { 18 | logger.Logger.Error("", errMsgField) 19 | } 20 | 21 | g.JSON(he.Status, he) 22 | } 23 | -------------------------------------------------------------------------------- /server/infra/db/tx_closer.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 6 | ) 7 | 8 | // CloseTransaction executes post process of tx. 9 | func CloseTransaction(tx query.TxManager, err error) error { 10 | if p := recover(); p != nil { // rewrite panic 11 | err = tx.Rollback() 12 | err = errors.Wrap(err, "failed to roll back") 13 | panic(p) 14 | } else if err != nil { 15 | err = tx.Rollback() 16 | err = errors.Wrap(err, "failed to roll back") 17 | } else { 18 | err = tx.Commit() 19 | err = errors.Wrap(err, "failed to commit") 20 | } 21 | return err 22 | } 23 | -------------------------------------------------------------------------------- /server/util/crypt.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "golang.org/x/crypto/bcrypt" 6 | ) 7 | 8 | // HashPassword returns hashed password. 9 | func HashPassword(password string) (string, error) { 10 | hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) 11 | return string(hash), errors.Wrap(err, "failed to generate from password") 12 | } 13 | 14 | // CheckHashOfPassword checks whether given hashed is the value of hashed password or not. 15 | func CheckHashOfPassword(password, hashed string) bool { 16 | err := bcrypt.CompareHashAndPassword([]byte(hashed), []byte(password)) 17 | return err == nil 18 | } 19 | -------------------------------------------------------------------------------- /server/domain/repository/user.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 7 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 8 | ) 9 | 10 | // UserRepository is repository of user. 11 | type UserRepository interface { 12 | GetUserByID(ctx context.Context, m query.SQLManager, id uint32) (*model.User, error) 13 | GetUserByName(ctx context.Context, m query.SQLManager, name string) (*model.User, error) 14 | InsertUser(ctx context.Context, m query.SQLManager, user *model.User) (uint32, error) 15 | UpdateUser(ctx context.Context, m query.SQLManager, id uint32, user *model.User) error 16 | DeleteUser(ctx context.Context, m query.SQLManager, id uint32) error 17 | } 18 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/store/action-types.js: -------------------------------------------------------------------------------- 1 | // index.js 2 | export const LOGIN = 'LOGIN' 3 | export const SIGN_UP = 'SIGN_UP' 4 | export const LOGOUT = 'LOGOUT' 5 | 6 | // threads.js 7 | export const LIST_THREADS = 'LIST_THREADS' 8 | export const LIST_THREADS_MORE = 'LIST_THREADS_MORE' 9 | export const SAVE_THREAD = 'SAVE_THREAD' 10 | export const EDIT_THREAD = 'EDIT_THREAD' 11 | export const DELETE_THREAD = 'DELETE_THREAD' 12 | export const CHANGE_IS_DIALOG_VISIBLE = 'CHANGE_IS_DIALOG_VISIBLE' 13 | 14 | // comments.js 15 | export const LIST_COMMENTS = 'LIST_COMMENTS' 16 | export const LIST_COMMENTS_MORE = 'LIST_COMMENTS_MORE' 17 | export const SAVE_COMMENT = 'SAVE_COMMENT' 18 | export const EDIT_COMMENT = 'EDIT_COMMENT' 19 | export const DELETE_COMMENT = 'DELETE_COMMENT' 20 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/pages/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 33 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | nvgdb: 5 | image: mysql:8.0 6 | environment: 7 | MYSQL_DATABASE: "nuxt_vue_go_chat" 8 | MYSQL_ALLOW_EMPTY_PASSWORD: "yes" 9 | volumes: 10 | - "./mysql:/etc/mysql/conf.d" 11 | - "./mysql/data:/var/lib/mysql" 12 | - "./mysql/init:/docker-entrypoint-initdb.d" 13 | container_name: gvdb 14 | ports: 15 | - "3306:3306" 16 | app: 17 | build: 18 | context: ./ 19 | dockerfile: docker/Dockerfile 20 | volumes: 21 | - ./:/go/src/github.com/sekky0905/nuxt-vue-go-chat 22 | command: bash -c 'cd /go/src/github.com/sekky0905/nuxt-vue-go-chat/server && go run *.go' 23 | ports: 24 | - "8080:8080" 25 | depends_on: 26 | - nvgdb 27 | container_name: app 28 | -------------------------------------------------------------------------------- /server/Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [prune] 29 | go-tests = true 30 | unused-packages = true 31 | -------------------------------------------------------------------------------- /server/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: deps 2 | deps: tools 3 | go get github.com/golang/dep/cmd/dep 4 | dep ensure 5 | 6 | .PHONY: tools 7 | tools: 8 | go get golang.org/x/lint/golint 9 | go get golang.org/x/tools/cmd/goimports 10 | go get github.com/kisielk/errcheck 11 | 12 | .PHONY: test 13 | test: 14 | go test ./... 15 | 16 | .PHONY: check 17 | check: 18 | find . -name '*.go' | grep -v 'vendor' | xargs gofmt -l 19 | find . -name '*.go' | grep -v 'vendor' | xargs goimports -l 20 | go list ./... | grep -v 'vendor' | xargs golint 21 | go list ./... | grep -v 'vendor' | xargs go vet 22 | go list ./... | grep -v 'vendor' | grep -v 'mock' | xargs errcheck -asserts -blank -exclude errcheck_excludes.txt 23 | 24 | .PHONY:run 25 | run: 26 | cd ../client/nuxt-vue-go-chat && yarn lint && yarn build && cd ../.. && docker-compose up 27 | -------------------------------------------------------------------------------- /server/interface/controller/error_code.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | // ErrCode is error code. 4 | type ErrCode string 5 | 6 | // Server error. 7 | const ( 8 | InternalFailure ErrCode = "InternalFailure" 9 | InternalDBFailure ErrCode = "InternalDBFailure" 10 | InternalSQLFailure ErrCode = "InternalSQLFailure" 11 | ServerError ErrCode = "ServerError" 12 | ) 13 | 14 | // User error. 15 | const ( 16 | InvalidParameterValueFailure ErrCode = "InvalidParameterValueFailure" 17 | InvalidParametersValueFailure ErrCode = "InvalidParametersValueFailure" 18 | NoSuchDataFailure ErrCode = "NoSuchDataFailure" 19 | RequiredFailure ErrCode = "RequiredError" 20 | AlreadyExistsFailure ErrCode = "AlreadyExistsFailure" 21 | AuthenticationFailure ErrCode = "AuthenticationFailure" 22 | ) 23 | -------------------------------------------------------------------------------- /server/domain/repository/comment.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 7 | 8 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 9 | ) 10 | 11 | // CommentRepository is Repository of Comment. 12 | type CommentRepository interface { 13 | ListComments(ctx context.Context, m query.SQLManager, threadID uint32, limit int, cursor uint32) (*model.CommentList, error) 14 | GetCommentByID(ctx context.Context, m query.SQLManager, id uint32) (*model.Comment, error) 15 | InsertComment(ctx context.Context, m query.SQLManager, comment *model.Comment) (uint32, error) 16 | UpdateComment(ctx context.Context, m query.SQLManager, id uint32, comment *model.Comment) error 17 | DeleteComment(ctx context.Context, m query.SQLManager, id uint32) error 18 | } 19 | -------------------------------------------------------------------------------- /server/domain/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "go.uber.org/zap/zapcore" 7 | ) 8 | 9 | // User is User model. 10 | type User struct { 11 | ID uint32 `json:"id"` 12 | Name string `json:"name" binding:"required"` 13 | SessionID string `json:"sessionId"` 14 | Password string `json:"password" binding:"required"` 15 | CreatedAt time.Time `json:"createdAt"` 16 | UpdatedAt time.Time `json:"updatedAt"` 17 | } 18 | 19 | // MarshalLogObject for zap logger. 20 | func (u User) MarshalLogObject(enc zapcore.ObjectEncoder) error { 21 | enc.AddInt32("id", int32(u.ID)) 22 | enc.AddString("name", u.Name) 23 | enc.AddString("sessionID", u.SessionID) 24 | enc.AddString("password", u.Password) 25 | enc.AddTime("createdAt", u.CreatedAt) 26 | enc.AddTime("updatedAt", u.UpdatedAt) 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /server/domain/repository/thread.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 7 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 8 | ) 9 | 10 | // ThreadRepository is Repository of Thread. 11 | type ThreadRepository interface { 12 | ListThreads(ctx context.Context, m query.SQLManager, cursor uint32, limit int) (*model.ThreadList, error) 13 | GetThreadByID(ctx context.Context, m query.SQLManager, id uint32) (*model.Thread, error) 14 | GetThreadByTitle(ctx context.Context, m query.SQLManager, name string) (*model.Thread, error) 15 | InsertThread(ctx context.Context, m query.SQLManager, thead *model.Thread) (uint32, error) 16 | UpdateThread(ctx context.Context, m query.SQLManager, id uint32, thead *model.Thread) error 17 | DeleteThread(ctx context.Context, m query.SQLManager, id uint32) error 18 | } 19 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | // index.js 2 | export const SET_USER = 'SET_USER' 3 | 4 | // threads.js 5 | export const SET_THREADS = 'SET_THREADS' 6 | export const SET_THREAD_LIST = 'SET_THREAD_LIST' 7 | export const ADD_THREAD_LIST = 'ADD_THREAD_LIST' 8 | export const ADD_THREAD = 'ADD_THREAD' 9 | export const UPDATE_THREAD = 'UPDATE_THREAD' 10 | export const REMOVE_THREAD = 'REMOVE_THREAD' 11 | export const CLEAR_THREADS = 'CLEAR_THREADS' 12 | export const SET_IS_DIALOG_VISIBLE = 'SET_IS_DIALOG_VISIBLE' 13 | 14 | // comments.js 15 | export const SET_COMMENTS = 'SET_COMMENTS' 16 | export const SET_COMMENT_LIST = 'SET_COMMENT_LIST' 17 | export const ADD_COMMENT_LIST = 'ADD_COMMENT_LIST' 18 | export const ADD_COMMENT = 'ADD_COMMENT' 19 | export const UPDATE_COMMENT = 'UPDATE_COMMENT' 20 | export const REMOVE_COMMENT = 'REMOVE_COMMENT' 21 | export const CLEAR_COMMENTS = 'CLEAR_COMMENTS' 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Go template 3 | # Binaries for programs and plugins 4 | *.exe 5 | *.exe~ 6 | *.dll 7 | *.so 8 | *.dylib 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | ### macOS template 16 | # General 17 | .DS_Store 18 | .AppleDouble 19 | .LSOverride 20 | 21 | # Icon must end with two \r 22 | Icon 23 | 24 | # Thumbnails 25 | ._* 26 | 27 | # Files that might appear in the root of a volume 28 | .DocumentRevisions-V100 29 | .fseventsd 30 | .Spotlight-V100 31 | .TemporaryItems 32 | .Trashes 33 | .VolumeIcon.icns 34 | .com.apple.timemachine.donotpresent 35 | 36 | # Directories potentially created on remote AFP share 37 | .AppleDB 38 | .AppleDesktop 39 | Network Trash Folder 40 | Temporary Items 41 | .apdisk 42 | 43 | .idea/ 44 | server/vendor/ 45 | mysql/data 46 | -------------------------------------------------------------------------------- /server/interface/controller/validator.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 5 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/logger" 6 | "gopkg.in/go-playground/validator.v8" 7 | ) 8 | 9 | // type ValidationErrors map[string]*FieldError 10 | 11 | // handleValidatorErr handle validator error. 12 | func handleValidatorErr(err error) error { 13 | errors, ok := err.(validator.ValidationErrors) 14 | if !ok { 15 | logger.Logger.Error("failed to assert ValidationErrors") 16 | } 17 | 18 | errs := &model.InvalidParamsError{} 19 | 20 | for _, v := range errors { 21 | e := &model.InvalidParamError{ 22 | BaseErr: err, 23 | PropertyName: model.PropertyName(v.Field), 24 | PropertyValue: v.Value, 25 | } 26 | 27 | errs.Errors = append(errs.Errors, e) 28 | } 29 | 30 | if len(errs.Errors) == 1 { 31 | return errs.Errors[0] 32 | } 33 | 34 | return errs 35 | } 36 | -------------------------------------------------------------------------------- /server/middleware/authentication.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 6 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db" 7 | "github.com/sekky0905/nuxt-vue-go-chat/server/interface/controller" 8 | ) 9 | 10 | // CheckAuthentication checks authentication of user who requested. 11 | func CheckAuthentication() gin.HandlerFunc { 12 | return func(g *gin.Context) { 13 | id, err := g.Cookie(model.SessionIDAtCookie) 14 | if err != nil { 15 | controller.ResponseAndLogError(g, &model.AuthenticationErr{}) 16 | g.Abort() 17 | return 18 | } 19 | 20 | ctx := g.Request.Context() 21 | repo := db.NewSessionRepository() 22 | m := db.NewDBManager() 23 | session, err := repo.GetSessionByID(ctx, m, id) 24 | if err != nil || session == nil { 25 | controller.ResponseAndLogError(g, &model.AuthenticationErr{}) 26 | g.Abort() 27 | return 28 | } 29 | g.Next() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /server/infra/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | "go.uber.org/zap/zapcore" 6 | ) 7 | 8 | // Logger is Log object. 9 | var Logger *zap.Logger 10 | 11 | func init() { 12 | level := zap.NewAtomicLevel() 13 | level.SetLevel(zapcore.DebugLevel) 14 | 15 | myConfig := zap.Config{ 16 | Level: level, 17 | Encoding: "json", 18 | EncoderConfig: zapcore.EncoderConfig{ 19 | TimeKey: "Time", 20 | LevelKey: "Level", 21 | NameKey: "Name", 22 | CallerKey: "Caller", 23 | MessageKey: "Msg", 24 | StacktraceKey: "St", 25 | EncodeLevel: zapcore.CapitalLevelEncoder, 26 | EncodeTime: zapcore.ISO8601TimeEncoder, 27 | EncodeDuration: zapcore.StringDurationEncoder, 28 | EncodeCaller: zapcore.ShortCallerEncoder, 29 | }, 30 | OutputPaths: []string{"stdout"}, 31 | ErrorOutputPaths: []string{"stderr"}, 32 | } 33 | var err error 34 | Logger, err = myConfig.Build() 35 | if err != nil { 36 | panic(err) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/store/index.js: -------------------------------------------------------------------------------- 1 | import { SET_USER } from './mutation-types.js' 2 | import { LOGIN, SIGN_UP, LOGOUT } from './action-types.js' 3 | 4 | export const state = () => ({ 5 | isLoggedIn: false, 6 | user: null 7 | }) 8 | 9 | export const getters = { 10 | isLoggedIn: state => state.isLoggedIn, 11 | user: state => state.user 12 | } 13 | 14 | export const mutations = { 15 | [SET_USER](state, { user, isLoggedIn }) { 16 | state.user = user 17 | state.isLoggedIn = isLoggedIn 18 | } 19 | } 20 | 21 | export const actions = { 22 | async [LOGIN]({ commit }, { name, password }) { 23 | const payload = { 24 | name: name, 25 | password: password 26 | } 27 | const response = await this.$axios.$post('/login', payload) 28 | commit(SET_USER, { user: response, isLoggedIn: true }) 29 | }, 30 | async [SIGN_UP]({ commit, state }, { name, password }) { 31 | const payload = { 32 | name: name, 33 | password: password 34 | } 35 | const response = await this.$axios.$post('/signUp', payload) 36 | commit(SET_USER, { user: response, isLoggedIn: true }) 37 | }, 38 | async [LOGOUT]({ commit }) { 39 | await this.$axios.$delete('/logout') 40 | commit(SET_USER, { user: null, isLoggedIn: false }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /server/infra/db/query/db_manager.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | ) 7 | 8 | // DBManager is the manager of SQL. 9 | type DBManager interface { 10 | SQLManager 11 | Beginner 12 | } 13 | 14 | // TxManager is the manager of Tx. 15 | type TxManager interface { 16 | SQLManager 17 | Commit() error 18 | Rollback() error 19 | } 20 | 21 | // SQLManager is the manager of DB. 22 | type SQLManager interface { 23 | Querier 24 | Preparer 25 | Executor 26 | } 27 | 28 | type ( 29 | // Executor is interface of Execute. 30 | Executor interface { 31 | Exec(query string, args ...interface{}) (sql.Result, error) 32 | ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) 33 | } 34 | 35 | // Preparer is interface of Prepare. 36 | Preparer interface { 37 | Prepare(query string) (*sql.Stmt, error) 38 | PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) 39 | } 40 | 41 | // Querier is interface of Query. 42 | Querier interface { 43 | Query(query string, args ...interface{}) (*sql.Rows, error) 44 | QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) 45 | } 46 | 47 | // Beginner is interface of Begin. 48 | Beginner interface { 49 | Begin() (TxManager, error) 50 | } 51 | ) 52 | -------------------------------------------------------------------------------- /mysql/init/setup.sql: -------------------------------------------------------------------------------- 1 | USE nuxt_vue_go_chat; 2 | 3 | CREATE TABLE IF NOT EXISTS users ( 4 | id INT UNSIGNED NOT NULL AUTO_INCREMENT, 5 | name VARCHAR(30) NOT NULL, 6 | session_id VARCHAR(36) NOT NULL, 7 | password VARCHAR(64) NOT NULL, 8 | created_at DATETIME DEFAULT NULL, 9 | updated_at DATETIME DEFAULT NULL, 10 | PRIMARY KEY (id) 11 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 12 | 13 | CREATE TABLE IF NOT EXISTS sessions ( 14 | id VARCHAR(36) NOT NULL, 15 | user_id INT UNSIGNED NOT NULL, 16 | created_at DATETIME DEFAULT NULL, 17 | PRIMARY KEY (id) 18 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 19 | 20 | CREATE TABLE IF NOT EXISTS threads ( 21 | id INT UNSIGNED NOT NULL AUTO_INCREMENT, 22 | title VARCHAR(20) NOT NULL, 23 | user_id INT UNSIGNED NOT NULL, 24 | created_at DATETIME DEFAULT NULL, 25 | updated_at DATETIME DEFAULT NULL, 26 | PRIMARY KEY (id), 27 | UNIQUE KEY (title) 28 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 29 | 30 | CREATE TABLE IF NOT EXISTS comments ( 31 | id INT UNSIGNED NOT NULL AUTO_INCREMENT, 32 | thread_id INT UNSIGNED NOT NULL, 33 | user_id INT UNSIGNED NOT NULL, 34 | content VARCHAR(200) NOT NULL, 35 | created_at DATETIME DEFAULT NULL, 36 | updated_at DATETIME DEFAULT NULL, 37 | PRIMARY KEY (id) 38 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 39 | -------------------------------------------------------------------------------- /server/testutil/model_helper.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 7 | ) 8 | 9 | // GenerateThreadHelper generates and returns Thread slice. 10 | func GenerateThreadHelper(startNum, endNum int) []*model.Thread { 11 | num := endNum - startNum + 1 12 | 13 | threads := make([]*model.Thread, num, num) 14 | i := 0 15 | for j := startNum; j < endNum+1; j++ { 16 | thread := &model.Thread{ 17 | ID: uint32(j), 18 | Title: fmt.Sprintf("%s%d", model.TitleForTest, j), 19 | User: &model.User{ 20 | ID: model.UserValidIDForTest, 21 | Name: model.UserNameForTest, 22 | }, 23 | } 24 | threads[i] = thread 25 | i++ 26 | } 27 | 28 | return threads 29 | } 30 | 31 | // GenerateCommentHelper generates and returns Comment slice. 32 | func GenerateCommentHelper(startNum, endNum int) []*model.Comment { 33 | num := endNum - startNum + 1 34 | 35 | comments := make([]*model.Comment, num, num) 36 | i := 0 37 | for j := startNum; j < endNum+1; j++ { 38 | comment := &model.Comment{ 39 | ID: uint32(j), 40 | ThreadID: uint32(j), 41 | User: &model.User{ 42 | ID: model.UserValidIDForTest, 43 | Name: model.UserNameForTest, 44 | }, 45 | Content: model.CommentContentForTest, 46 | } 47 | comments[i] = comment 48 | i++ 49 | } 50 | 51 | return comments 52 | } 53 | -------------------------------------------------------------------------------- /server/domain/model/thread.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "go.uber.org/zap" 7 | "go.uber.org/zap/zapcore" 8 | ) 9 | 10 | // Thread is thread model. 11 | type Thread struct { 12 | ID uint32 `json:"id"` 13 | Title string `json:"title"` 14 | *User `json:"user"` 15 | CreatedAt time.Time `json:"createdAt"` 16 | UpdatedAt time.Time `json:"updatedAt"` 17 | } 18 | 19 | // MarshalLogObject for zap logger. 20 | func (t Thread) MarshalLogObject(enc zapcore.ObjectEncoder) error { 21 | enc.AddInt32("id", int32(t.ID)) 22 | enc.AddString("title", t.Title) 23 | if err := enc.AddObject("user", t.User); err != nil { 24 | return err 25 | } 26 | enc.AddTime("createdAt", t.CreatedAt) 27 | enc.AddTime("updatedAt", t.UpdatedAt) 28 | return nil 29 | } 30 | 31 | // ThreadList is list of thread. 32 | type ThreadList struct { 33 | Threads []*Thread `json:"threads"` 34 | HasNext bool `json:"hasNext"` 35 | Cursor uint32 `json:"cursor"` 36 | } 37 | 38 | // MarshalLogObject for zap logger. 39 | func (tl ThreadList) MarshalLogObject(enc zapcore.ObjectEncoder) error { 40 | zap.Array("threads", zapcore.ArrayMarshalerFunc(func(inner zapcore.ArrayEncoder) error { 41 | for _, t := range tl.Threads { 42 | if err := enc.AddObject("thread", t); err != nil { 43 | return err 44 | } 45 | } 46 | return nil 47 | })) 48 | 49 | enc.AddBool("hasNext", tl.HasNext) 50 | enc.AddInt32("cursor", int32(tl.Cursor)) 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /server/domain/model/comment.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "go.uber.org/zap" 7 | "go.uber.org/zap/zapcore" 8 | ) 9 | 10 | // Comment is comment model. 11 | type Comment struct { 12 | ID uint32 `json:"id"` 13 | Content string `json:"content"` 14 | ThreadID uint32 `json:"threadId"` 15 | *User `json:"user"` 16 | CreatedAt time.Time `json:"createdAt"` 17 | UpdatedAt time.Time `json:"updatedAt"` 18 | } 19 | 20 | // MarshalLogObject for zap logger. 21 | func (c Comment) MarshalLogObject(enc zapcore.ObjectEncoder) error { 22 | enc.AddInt32("id", int32(c.ID)) 23 | enc.AddString("content", c.Content) 24 | enc.AddInt32("threadID)", int32(c.ThreadID)) 25 | if err := enc.AddObject("user", c.User); err != nil { 26 | return err 27 | } 28 | enc.AddTime("createdAt", c.CreatedAt) 29 | enc.AddTime("updatedAt", c.UpdatedAt) 30 | return nil 31 | } 32 | 33 | // CommentList is list of comment. 34 | type CommentList struct { 35 | Comments []*Comment `json:"comments"` 36 | HasNext bool `json:"hasNext"` 37 | Cursor uint32 `json:"cursor"` 38 | } 39 | 40 | // MarshalLogObject for zap logger. 41 | func (cl CommentList) MarshalLogObject(enc zapcore.ObjectEncoder) error { 42 | zap.Array("comments", zapcore.ArrayMarshalerFunc(func(inner zapcore.ArrayEncoder) error { 43 | for _, c := range cl.Comments { 44 | if err := enc.AddObject("comment", c); err != nil { 45 | return err 46 | } 47 | } 48 | return nil 49 | })) 50 | 51 | enc.AddBool("hasNext", cl.HasNext) 52 | enc.AddInt32("cursor", int32(cl.Cursor)) 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/.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 81 | .idea 82 | 83 | # Service worker 84 | sw.* 85 | -------------------------------------------------------------------------------- /server/domain/service/authentication.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | 8 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 9 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/repository" 10 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 11 | "github.com/sekky0905/nuxt-vue-go-chat/server/util" 12 | ) 13 | 14 | // AuthenticationService is interface of domain service of authentication. 15 | type AuthenticationService interface { 16 | Authenticate(ctx context.Context, m query.SQLManager, userName, password string) (ok bool, user *model.User, err error) 17 | } 18 | 19 | // authenticationService is domain service of authentication. 20 | type authenticationService struct { 21 | repo repository.UserRepository 22 | } 23 | 24 | // NewAuthenticationService generates and returns AuthenticationService. 25 | func NewAuthenticationService(repo repository.UserRepository) AuthenticationService { 26 | return &authenticationService{ 27 | repo: repo, 28 | } 29 | } 30 | 31 | // Authenticate authenticate user. 32 | func (s *authenticationService) Authenticate(ctx context.Context, m query.SQLManager, userName, password string) (ok bool, user *model.User, err error) { 33 | gotUser, err := s.repo.GetUserByName(ctx, m, userName) 34 | if err != nil { 35 | if _, ok := errors.Cause(err).(*model.NoSuchDataError); ok { 36 | return false, nil, &model.AuthenticationErr{ 37 | BaseErr: err, 38 | } 39 | } 40 | 41 | return false, nil, errors.Wrap(err, "failed, to get user by name") 42 | } 43 | 44 | if !util.CheckHashOfPassword(password, gotUser.Password) { 45 | return false, nil, nil 46 | } 47 | 48 | return true, gotUser, nil 49 | } 50 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-vue-go-chat", 3 | "version": "1.0.0", 4 | "description": "My impressive Nuxt.js project", 5 | "author": "sekky0905", 6 | "private": true, 7 | "scripts": { 8 | "dev": "npm run lint && nuxt", 9 | "build": "nuxt build", 10 | "start": "nuxt start", 11 | "generate": "nuxt generate", 12 | "lint": "eslint --fix --ext .js,.vue --ignore-path .gitignore .", 13 | "precommit": "npm run lint", 14 | "test": "jest" 15 | }, 16 | "dependencies": { 17 | "@nuxtjs/axios": "^5.3.6", 18 | "@nuxtjs/pwa": "^2.6.0", 19 | "cross-env": "^5.2.0", 20 | "moment": "^2.24.0", 21 | "nuxt": "^2.4.0", 22 | "vue-infinite-loading": "^2.4.3", 23 | "vuelidate": "^0.7.4", 24 | "vuetify": "^1.5.5", 25 | "vuetify-loader": "^1.2.1" 26 | }, 27 | "devDependencies": { 28 | "@nuxtjs/eslint-config": "^0.0.1", 29 | "@vue/test-utils": "^1.0.0-beta.27", 30 | "babel-core": "7.0.0-bridge.0", 31 | "babel-eslint": "^10.0.1", 32 | "babel-jest": "^24.1.0", 33 | "eslint": "^5.15.1", 34 | "eslint-config-prettier": "^4.1.0", 35 | "eslint-config-standard": ">=12.0.0", 36 | "eslint-loader": "^2.1.2", 37 | "eslint-plugin-import": ">=2.16.0", 38 | "eslint-plugin-jest": ">=22.3.0", 39 | "eslint-plugin-node": ">=8.0.1", 40 | "eslint-plugin-nuxt": ">=0.4.2", 41 | "eslint-plugin-prettier": "^3.0.1", 42 | "eslint-plugin-promise": ">=4.0.1", 43 | "eslint-plugin-standard": ">=4.0.0", 44 | "eslint-plugin-vue": "^5.2.2", 45 | "jest": "^24.1.0", 46 | "nodemon": "^1.18.9", 47 | "prettier": "^1.16.4", 48 | "stylus": "^0.54.5", 49 | "stylus-loader": "^3.0.2", 50 | "vue-jest": "^3.0.3" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /server/domain/service/comment.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 8 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/repository" 9 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 10 | ) 11 | 12 | // CommentService is interface of CommentService. 13 | type CommentService interface { 14 | IsAlreadyExistID(ctx context.Context, m query.SQLManager, id uint32) (bool, error) 15 | } 16 | 17 | // commentService is domain service of Comment. 18 | type commentService struct { 19 | repo repository.CommentRepository 20 | } 21 | 22 | // NewCommentService generates and returns CommentService. 23 | func NewCommentService(repo repository.CommentRepository) CommentService { 24 | return &commentService{ 25 | repo: repo, 26 | } 27 | } 28 | 29 | // NewComment generates and returns CommentService. 30 | func NewComment(content string, threadID uint32, user *model.User) *model.Comment { 31 | return &model.Comment{ 32 | Content: content, 33 | ThreadID: threadID, 34 | User: user, 35 | } 36 | } 37 | 38 | // NewCommentList generates and returns CommentService. 39 | func NewCommentList(list []*model.Comment, hasNext bool, cursor uint32) *model.CommentList { 40 | return &model.CommentList{ 41 | Comments: list, 42 | HasNext: hasNext, 43 | Cursor: cursor, 44 | } 45 | } 46 | 47 | // IsAlreadyExistID checks duplication of id. 48 | func (s commentService) IsAlreadyExistID(ctx context.Context, m query.SQLManager, id uint32) (bool, error) { 49 | searched, err := s.repo.GetCommentByID(ctx, m, id) 50 | if err != nil { 51 | return false, errors.Wrap(err, "failed to get comment by PropertyName") 52 | } 53 | return searched != nil, nil 54 | } 55 | -------------------------------------------------------------------------------- /server/domain/service/mock/comment.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: server/domain/service/comment.go 3 | 4 | // Package mock_service is a generated GoMock package. 5 | package mock_service 6 | 7 | import ( 8 | context "context" 9 | gomock "github.com/golang/mock/gomock" 10 | query "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 11 | reflect "reflect" 12 | ) 13 | 14 | // MockCommentService is a mock of CommentService interface 15 | type MockCommentService struct { 16 | ctrl *gomock.Controller 17 | recorder *MockCommentServiceMockRecorder 18 | } 19 | 20 | // MockCommentServiceMockRecorder is the mock recorder for MockCommentService 21 | type MockCommentServiceMockRecorder struct { 22 | mock *MockCommentService 23 | } 24 | 25 | // NewMockCommentService creates a new mock instance 26 | func NewMockCommentService(ctrl *gomock.Controller) *MockCommentService { 27 | mock := &MockCommentService{ctrl: ctrl} 28 | mock.recorder = &MockCommentServiceMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use 33 | func (m *MockCommentService) EXPECT() *MockCommentServiceMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // IsAlreadyExistID mocks base method 38 | func (m_2 *MockCommentService) IsAlreadyExistID(ctx context.Context, m query.SQLManager, id uint32) (bool, error) { 39 | m_2.ctrl.T.Helper() 40 | ret := m_2.ctrl.Call(m_2, "IsAlreadyExistID", ctx, m, id) 41 | ret0, _ := ret[0].(bool) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // IsAlreadyExistID indicates an expected call of IsAlreadyExistID 47 | func (mr *MockCommentServiceMockRecorder) IsAlreadyExistID(ctx, m, id interface{}) *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAlreadyExistID", reflect.TypeOf((*MockCommentService)(nil).IsAlreadyExistID), ctx, m, id) 50 | } 51 | -------------------------------------------------------------------------------- /server/domain/service/session.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/pkg/errors" 8 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 9 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/repository" 10 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 11 | "github.com/sekky0905/nuxt-vue-go-chat/server/util" 12 | ) 13 | 14 | // SessionService is interface of domain service of session. 15 | type SessionService interface { 16 | NewSession(userID uint32) *model.Session 17 | SessionID() string 18 | IsAlreadyExistID(ctx context.Context, m query.SQLManager, id string) (bool, error) 19 | } 20 | 21 | // SessionRepoFactory is factory of SessionRepository. 22 | type SessionRepoFactory func(ctx context.Context) repository.SessionRepository 23 | 24 | // sessionService is domain service of session. 25 | type sessionService struct { 26 | repo repository.SessionRepository 27 | } 28 | 29 | // NewSessionService generates and returns SessionService. 30 | func NewSessionService(repo repository.SessionRepository) SessionService { 31 | return &sessionService{ 32 | repo: repo, 33 | } 34 | } 35 | 36 | // NewSession generates and returns Session. 37 | func (s *sessionService) NewSession(userID uint32) *model.Session { 38 | session := &model.Session{ 39 | UserID: userID, 40 | CreatedAt: time.Now(), 41 | } 42 | return session 43 | } 44 | 45 | // SessionID generates and returns SessionID. 46 | func (s *sessionService) SessionID() string { 47 | return util.UUID() 48 | } 49 | 50 | // IsAlreadyExistID checks whether the data specified by id already exists or not. 51 | func (s sessionService) IsAlreadyExistID(ctx context.Context, m query.SQLManager, id string) (bool, error) { 52 | var searched *model.Session 53 | var err error 54 | 55 | if searched, err = s.repo.GetSessionByID(ctx, m, id); err != nil { 56 | return false, errors.Wrap(err, "failed to get session by id") 57 | } 58 | return searched != nil, nil 59 | } 60 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/components/Toolbar.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /server/domain/service/mock/authentication.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: server/domain/service/authentication.go 3 | 4 | // Package mock_service is a generated GoMock package. 5 | package mock_service 6 | 7 | import ( 8 | context "context" 9 | gomock "github.com/golang/mock/gomock" 10 | model "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 11 | query "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 12 | reflect "reflect" 13 | ) 14 | 15 | // MockAuthenticationService is a mock of AuthenticationService interface 16 | type MockAuthenticationService struct { 17 | ctrl *gomock.Controller 18 | recorder *MockAuthenticationServiceMockRecorder 19 | } 20 | 21 | // MockAuthenticationServiceMockRecorder is the mock recorder for MockAuthenticationService 22 | type MockAuthenticationServiceMockRecorder struct { 23 | mock *MockAuthenticationService 24 | } 25 | 26 | // NewMockAuthenticationService creates a new mock instance 27 | func NewMockAuthenticationService(ctrl *gomock.Controller) *MockAuthenticationService { 28 | mock := &MockAuthenticationService{ctrl: ctrl} 29 | mock.recorder = &MockAuthenticationServiceMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use 34 | func (m *MockAuthenticationService) EXPECT() *MockAuthenticationServiceMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // Authenticate mocks base method 39 | func (m_2 *MockAuthenticationService) Authenticate(ctx context.Context, m query.SQLManager, userName, password string) (bool, *model.User, error) { 40 | m_2.ctrl.T.Helper() 41 | ret := m_2.ctrl.Call(m_2, "Authenticate", ctx, m, userName, password) 42 | ret0, _ := ret[0].(bool) 43 | ret1, _ := ret[1].(*model.User) 44 | ret2, _ := ret[2].(error) 45 | return ret0, ret1, ret2 46 | } 47 | 48 | // Authenticate indicates an expected call of Authenticate 49 | func (mr *MockAuthenticationServiceMockRecorder) Authenticate(ctx, m, userName, password interface{}) *gomock.Call { 50 | mr.mock.ctrl.T.Helper() 51 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Authenticate", reflect.TypeOf((*MockAuthenticationService)(nil).Authenticate), ctx, m, userName, password) 52 | } 53 | -------------------------------------------------------------------------------- /server/domain/model/const.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // const 4 | const ( 5 | InvalidID = 0 6 | SessionIDAtCookie = "SESSION_ID" 7 | ) 8 | 9 | // InvalidReason is InvalidReason message for developer. 10 | type InvalidReason string 11 | 12 | // String return as string. 13 | func (p InvalidReason) String() string { 14 | return string(p) 15 | } 16 | 17 | // DomainModelName is Model name for developer. 18 | type DomainModelName string 19 | 20 | // String return as string. 21 | func (p DomainModelName) String() string { 22 | return string(p) 23 | } 24 | 25 | // Model name. 26 | const ( 27 | DomainModelNameUser DomainModelName = "User" 28 | DomainModelNameSession DomainModelName = "Session" 29 | DomainModelNameThread DomainModelName = "Thread" 30 | DomainModelNameComment DomainModelName = "Comment" 31 | ) 32 | 33 | // PropertyName is property name for developer. 34 | type PropertyName string 35 | 36 | // String return as string. 37 | func (p PropertyName) String() string { 38 | return string(p) 39 | } 40 | 41 | // Property name for developer. 42 | const ( 43 | IDProperty PropertyName = "ID" 44 | NameProperty PropertyName = "Name" 45 | TitleProperty PropertyName = "Title" 46 | PassWordProperty PropertyName = "Password" 47 | ThreadIDProperty PropertyName = "ThreadID" 48 | ) 49 | 50 | // FailedToBeginTx is error of tx begin. 51 | const FailedToBeginTx InvalidReason = "failed to begin tx" 52 | 53 | // User 54 | const ( 55 | UserNameForTest = "testUserName" 56 | PasswordForTest = "testPasswor" 57 | UserValidIDForTest uint32 = 1 58 | UserInValidIDForTest uint32 = 2 59 | ) 60 | 61 | // Session 62 | const ( 63 | SessionValidIDForTest = "testValidSessionID12345678" 64 | SessionInValidIDForTest = "testInvalidSessionID12345678" 65 | TitleForTest = "TitleForTest" 66 | ) 67 | 68 | // Thread 69 | const ( 70 | ThreadValidIDForTest uint32 = 1 71 | ThreadInValidIDForTest uint32 = 2 72 | ) 73 | 74 | // Comment 75 | const ( 76 | CommentValidIDForTest uint32 = 1 77 | CommentInValidIDForTest uint32 = 2 78 | CommentContentForTest = "ContentForTest" 79 | ) 80 | 81 | // error message for test 82 | const ( 83 | ErrorMessageForTest = "some error has occurred" 84 | ) 85 | -------------------------------------------------------------------------------- /server/domain/service/thread.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 8 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/repository" 9 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 10 | ) 11 | 12 | // ThreadService is interface of ThreadService. 13 | type ThreadService interface { 14 | IsAlreadyExistID(ctx context.Context, m query.SQLManager, id uint32) (bool, error) 15 | IsAlreadyExistTitle(ctx context.Context, m query.SQLManager, title string) (bool, error) 16 | } 17 | 18 | // threadService is domain service of Thread. 19 | type threadService struct { 20 | repo repository.ThreadRepository 21 | } 22 | 23 | // NewThreadService generates and returns ThreadService. 24 | func NewThreadService(repo repository.ThreadRepository) ThreadService { 25 | return &threadService{ 26 | repo: repo, 27 | } 28 | } 29 | 30 | // NewThread generates and returns Thread. 31 | func (s threadService) NewThread(title string, user *model.User) *model.Thread { 32 | return &model.Thread{ 33 | Title: title, 34 | User: user, 35 | } 36 | } 37 | 38 | // NewThreadList generates and returns ThreadList. 39 | func (s threadService) NewThreadList(list []*model.Thread, hasNext bool, cursor uint32) *model.ThreadList { 40 | return &model.ThreadList{ 41 | Threads: list, 42 | HasNext: hasNext, 43 | Cursor: cursor, 44 | } 45 | } 46 | 47 | // IsAlreadyExistID checks duplication of id. 48 | func (s threadService) IsAlreadyExistID(ctx context.Context, m query.SQLManager, id uint32) (bool, error) { 49 | var searched *model.Thread 50 | var err error 51 | 52 | if searched, err = s.repo.GetThreadByID(ctx, m, id); err != nil { 53 | return false, errors.Wrap(err, "failed to get thread by PropertyName") 54 | } 55 | return searched != nil, nil 56 | } 57 | 58 | // IsAlreadyExistID checks duplication of name. 59 | func (s threadService) IsAlreadyExistTitle(ctx context.Context, m query.SQLManager, title string) (bool, error) { 60 | var searched *model.Thread 61 | var err error 62 | 63 | if searched, err = s.repo.GetThreadByTitle(ctx, m, title); err != nil { 64 | return false, errors.Wrap(err, "failed to get thread by title") 65 | } 66 | return searched != nil, nil 67 | } 68 | -------------------------------------------------------------------------------- /server/domain/service/user.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/pkg/errors" 8 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 9 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/repository" 10 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 11 | "github.com/sekky0905/nuxt-vue-go-chat/server/util" 12 | ) 13 | 14 | // UserService is interface of domain service of user. 15 | type UserService interface { 16 | NewUser(name, password string) (*model.User, error) 17 | IsAlreadyExistID(ctx context.Context, m query.SQLManager, id uint32) (bool, error) 18 | IsAlreadyExistName(ctx context.Context, m query.SQLManager, name string) (bool, error) 19 | } 20 | 21 | // UserRepoFactory is factory of UserRepository. 22 | type UserRepoFactory func(ctx context.Context) repository.UserRepository 23 | 24 | // userService is domain service of user. 25 | type userService struct { 26 | repo repository.UserRepository 27 | } 28 | 29 | // NewUserService generates and returns UserService. 30 | func NewUserService(m query.SQLManager, repo repository.UserRepository) UserService { 31 | return &userService{ 32 | repo: repo, 33 | } 34 | } 35 | 36 | // NewUser generates and reruns User. 37 | func (s *userService) NewUser(name, password string) (*model.User, error) { 38 | hashed, err := util.HashPassword(password) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | return &model.User{ 44 | Name: name, 45 | Password: hashed, 46 | CreatedAt: time.Now(), 47 | UpdatedAt: time.Now(), 48 | }, nil 49 | } 50 | 51 | // IsAlreadyExistID checks whether the data specified by id already exists or not. 52 | func (s *userService) IsAlreadyExistID(ctx context.Context, m query.SQLManager, id uint32) (bool, error) { 53 | searched, err := s.repo.GetUserByID(ctx, m, id) 54 | if err != nil { 55 | return false, errors.Wrap(err, "failed to get user by id") 56 | } 57 | return searched != nil, nil 58 | } 59 | 60 | // IsAlreadyExistName checks whether the data specified by name already exists or not. 61 | func (s *userService) IsAlreadyExistName(ctx context.Context, m query.SQLManager, name string) (bool, error) { 62 | searched, err := s.repo.GetUserByName(ctx, m, name) 63 | if err != nil { 64 | return false, errors.Wrap(err, "failed to get user by Name") 65 | } 66 | 67 | return searched != nil, nil 68 | } 69 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/nuxt.config.js: -------------------------------------------------------------------------------- 1 | import colors from 'vuetify/es5/util/colors' 2 | import VuetifyLoaderPlugin from 'vuetify-loader/lib/plugin' 3 | import pkg from './package' 4 | 5 | export default { 6 | mode: 'spa', 7 | srcDir: 'app', 8 | /* 9 | ** Headers of the page 10 | */ 11 | head: { 12 | title: pkg.name, 13 | meta: [ 14 | { charset: 'utf-8' }, 15 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 16 | { hid: 'description', name: 'description', content: pkg.description } 17 | ], 18 | link: [ 19 | { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, 20 | { 21 | rel: 'stylesheet', 22 | href: 23 | 'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' 24 | } 25 | ] 26 | }, 27 | 28 | /* 29 | ** Customize the progress-bar color 30 | */ 31 | loading: { color: '#fff' }, 32 | 33 | /* 34 | ** Global CSS 35 | */ 36 | css: ['~/assets/style/app.styl'], 37 | 38 | /* 39 | ** Plugins to load before mounting the App 40 | */ 41 | plugins: ['@/plugins/vuetify', '@/plugins/vuelidate', '@/plugins/axios.js'], 42 | 43 | /* 44 | ** Nuxt.js modules 45 | */ 46 | modules: [ 47 | // Doc: https://axios.nuxtjs.org/usage 48 | '@nuxtjs/axios', 49 | '@nuxtjs/pwa' 50 | ], 51 | vuetify: { 52 | theme: { 53 | primary: colors.indigo, 54 | secondary: colors.grey, 55 | accent: colors.pink, 56 | error: colors.red 57 | } 58 | }, 59 | /* 60 | ** Axios module configuration 61 | */ 62 | axios: { 63 | // See https://github.com/nuxt-community/axios-module#options 64 | baseURL: 'http://localhost:8080/v1' 65 | }, 66 | 67 | /* 68 | ** Build configuration 69 | */ 70 | build: { 71 | transpile: ['vuetify/lib'], 72 | plugins: [new VuetifyLoaderPlugin()], 73 | loaders: { 74 | stylus: { 75 | import: ['~assets/style/variables.styl'] 76 | } 77 | }, 78 | /* 79 | ** You can extend webpack config here 80 | */ 81 | extend(config, ctx) { 82 | // Run ESLint on save 83 | if (ctx.isDev && ctx.isClient) { 84 | config.module.rules.push({ 85 | enforce: 'pre', 86 | test: /\.(js|vue)$/, 87 | loader: 'eslint-loader', 88 | exclude: /(node_modules)/ 89 | }) 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /server/interface/controller/dto.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 7 | ) 8 | 9 | // UserDTO is DTO of User. 10 | type UserDTO struct { 11 | ID uint32 `json:"id" binding:"required"` 12 | Name string `json:"name" binding:"required"` 13 | SessionID string `json:"sessionId"` 14 | CreatedAt time.Time `json:"createdAt"` 15 | UpdatedAt time.Time `json:"updatedAt"` 16 | } 17 | 18 | // TranslateFromUserToUserDTO translates from User to UserDTO. 19 | func TranslateFromUserToUserDTO(user *model.User) *UserDTO { 20 | return &UserDTO{ 21 | ID: user.ID, 22 | Name: user.Name, 23 | SessionID: user.SessionID, 24 | CreatedAt: user.CreatedAt, 25 | UpdatedAt: user.UpdatedAt, 26 | } 27 | } 28 | 29 | // ThreadDTO is DTO of Thread. 30 | type ThreadDTO struct { 31 | ID uint32 `json:"id"` 32 | Title string `json:"title" binding:"required"` 33 | *UserDTO `json:"user"` 34 | CreatedAt time.Time `json:"createdAt"` 35 | UpdatedAt time.Time `json:"updatedAt"` 36 | } 37 | 38 | // TranslateFromThreadDTOToThread translates from ThreadDTO to Thread. 39 | func TranslateFromThreadDTOToThread(dto *ThreadDTO) *model.Thread { 40 | return &model.Thread{ 41 | ID: dto.ID, 42 | Title: dto.Title, 43 | User: &model.User{ 44 | ID: dto.UserDTO.ID, 45 | Name: dto.UserDTO.Name, 46 | CreatedAt: dto.UserDTO.CreatedAt, 47 | UpdatedAt: dto.UserDTO.UpdatedAt, 48 | }, 49 | CreatedAt: dto.CreatedAt, 50 | UpdatedAt: dto.UpdatedAt, 51 | } 52 | } 53 | 54 | // CommentDTO is DTO of CommentD. 55 | type CommentDTO struct { 56 | ID uint32 `json:"id"` 57 | Content string `json:"content" binding:"required"` 58 | ThreadID uint32 `json:"threadId" binding:"required"` 59 | *UserDTO `json:"user"` 60 | CreatedAt time.Time `json:"createdAt"` 61 | UpdatedAt time.Time `json:"updatedAt"` 62 | } 63 | 64 | // TranslateFromCommentDTOToComment translates from CommentDTO to Comment. 65 | func TranslateFromCommentDTOToComment(dto *CommentDTO) *model.Comment { 66 | return &model.Comment{ 67 | ID: dto.ID, 68 | Content: dto.Content, 69 | User: &model.User{ 70 | ID: dto.UserDTO.ID, 71 | Name: dto.UserDTO.Name, 72 | CreatedAt: dto.CreatedAt, 73 | UpdatedAt: dto.UpdatedAt, 74 | }, 75 | ThreadID: dto.ThreadID, 76 | CreatedAt: dto.CreatedAt, 77 | UpdatedAt: dto.UpdatedAt, 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /server/infra/db/db_manager.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | 7 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 8 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 9 | 10 | // SQL Driver. 11 | _ "github.com/go-sql-driver/mysql" 12 | ) 13 | 14 | // dbManager is the manager of SQL. 15 | type dbManager struct { 16 | Conn *sql.DB 17 | } 18 | 19 | // NewDBManager generates and returns DBManager. 20 | func NewDBManager() query.DBManager { 21 | conn, err := sql.Open("mysql", "root:@tcp(nvgdb:3306)/nuxt_vue_go_chat?charset=utf8mb4&parseTime=True") 22 | if err != nil { 23 | panic(err.Error()) 24 | } 25 | 26 | return &dbManager{ 27 | Conn: conn, 28 | } 29 | } 30 | 31 | // Exec executes SQL. 32 | func (s dbManager) Exec(query string, args ...interface{}) (sql.Result, error) { 33 | return s.Conn.Exec(query, args...) 34 | } 35 | 36 | // ExecContext executes SQL with context. 37 | func (s *dbManager) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { 38 | return s.Conn.ExecContext(ctx, query, args...) 39 | } 40 | 41 | // Query executes query which return row. 42 | func (s *dbManager) Query(query string, args ...interface{}) (*sql.Rows, error) { 43 | rows, err := s.Conn.Query(query, args...) 44 | if err != nil { 45 | err = &model.SQLError{ 46 | BaseErr: err, 47 | InvalidReasonForDeveloper: "failed to execute query", 48 | } 49 | return nil, err 50 | } 51 | 52 | return rows, nil 53 | } 54 | 55 | // QueryContext executes query which return row with context. 56 | func (s *dbManager) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { 57 | rows, err := s.Conn.Query(query, args...) 58 | if err != nil { 59 | err = &model.SQLError{ 60 | BaseErr: err, 61 | InvalidReasonForDeveloper: "failed to execute query with context", 62 | } 63 | return nil, err 64 | } 65 | return rows, nil 66 | } 67 | 68 | // Prepare prepares statement for Query and Exec later. 69 | func (s *dbManager) Prepare(query string) (*sql.Stmt, error) { 70 | return s.Conn.Prepare(query) 71 | } 72 | 73 | // Prepare prepares statement for Query and Exec later with context. 74 | func (s *dbManager) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) { 75 | return s.Conn.PrepareContext(ctx, query) 76 | } 77 | 78 | // Begin begins tx. 79 | func (s *dbManager) Begin() (query.TxManager, error) { 80 | return s.Conn.Begin() 81 | } 82 | -------------------------------------------------------------------------------- /server/domain/service/mock/thread.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: server/domain/service/thread.go 3 | 4 | // Package mock_service is a generated GoMock package. 5 | package mock_service 6 | 7 | import ( 8 | context "context" 9 | gomock "github.com/golang/mock/gomock" 10 | query "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 11 | reflect "reflect" 12 | ) 13 | 14 | // MockThreadService is a mock of ThreadService interface 15 | type MockThreadService struct { 16 | ctrl *gomock.Controller 17 | recorder *MockThreadServiceMockRecorder 18 | } 19 | 20 | // MockThreadServiceMockRecorder is the mock recorder for MockThreadService 21 | type MockThreadServiceMockRecorder struct { 22 | mock *MockThreadService 23 | } 24 | 25 | // NewMockThreadService creates a new mock instance 26 | func NewMockThreadService(ctrl *gomock.Controller) *MockThreadService { 27 | mock := &MockThreadService{ctrl: ctrl} 28 | mock.recorder = &MockThreadServiceMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use 33 | func (m *MockThreadService) EXPECT() *MockThreadServiceMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // IsAlreadyExistID mocks base method 38 | func (m_2 *MockThreadService) IsAlreadyExistID(ctx context.Context, m query.SQLManager, id uint32) (bool, error) { 39 | m_2.ctrl.T.Helper() 40 | ret := m_2.ctrl.Call(m_2, "IsAlreadyExistID", ctx, m, id) 41 | ret0, _ := ret[0].(bool) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // IsAlreadyExistID indicates an expected call of IsAlreadyExistID 47 | func (mr *MockThreadServiceMockRecorder) IsAlreadyExistID(ctx, m, id interface{}) *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAlreadyExistID", reflect.TypeOf((*MockThreadService)(nil).IsAlreadyExistID), ctx, m, id) 50 | } 51 | 52 | // IsAlreadyExistTitle mocks base method 53 | func (m_2 *MockThreadService) IsAlreadyExistTitle(ctx context.Context, m query.SQLManager, title string) (bool, error) { 54 | m_2.ctrl.T.Helper() 55 | ret := m_2.ctrl.Call(m_2, "IsAlreadyExistTitle", ctx, m, title) 56 | ret0, _ := ret[0].(bool) 57 | ret1, _ := ret[1].(error) 58 | return ret0, ret1 59 | } 60 | 61 | // IsAlreadyExistTitle indicates an expected call of IsAlreadyExistTitle 62 | func (mr *MockThreadServiceMockRecorder) IsAlreadyExistTitle(ctx, m, title interface{}) *gomock.Call { 63 | mr.mock.ctrl.T.Helper() 64 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAlreadyExistTitle", reflect.TypeOf((*MockThreadService)(nil).IsAlreadyExistTitle), ctx, m, title) 65 | } 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nuxt Vue Go Chat 2 | 3 | [![Build Status](https://travis-ci.org/sekky0905/nuxt-vue-go-chat.svg?branch=master)](https://travis-ci.org/sekky0905/nuxt-vue-go-chat) 4 | 5 | ## 概要 6 | 7 | Nuxt.js(Vue.js)とLayered Architectureのお勉強のために作成した簡単なチャットアプリ。 8 | 9 | ## 技術構成 10 | 11 | SPA(Nuxt.js(Vue.js)) + API(Go) + RDB(MySQL)という形になっている。 12 | 13 | ### フロントエンド 14 | 15 | #### 使用技術 16 | 17 | - [Vue.js](https://jp.vuejs.org/index.html) 18 | - [Nuxt.js](https://ja.nuxtjs.org/) 19 | - [vuetify](https://github.com/vuetifyjs/vuetify) 20 | 21 | 主に使用しているもののみ記載している。 22 | 23 | ### サーバーサイド 24 | 25 | - [Go](https://github.com/golang/go) 26 | - [gin](https://github.com/gin-gonic/gin) 27 | 28 | ## その他 29 | 30 | Update系の処理を画面側で実装していないが、サーバー側で実装しているのはあくまでもお勉強のため。 31 | 32 | ## アーキテクチャ 33 | `Layered Architecture` をベースにする。 34 | ただし、レイヤ間の結合度を下げるために各レイヤ間でDIPを行う。 35 | 36 | ``` 37 | ├── interface 38 | │ └── controller // サーバへの入力と出力を扱う責務。 39 | ├── application // 薄く保ち、やるべき作業の調整を行う責務。 40 | ├── domain 41 | │ ├── model // ビジネスの概念とビジネスロジック。 42 | │ ├── service // EntityでもValue Objectでもないドメイン層のロジック。 43 | │ └── repository // infra/dbへのポート。 44 | ├── infra // 技術的なものの提供 45 | │ ├── db // DBの技術に関すること。 46 | │ └── router // Routingの技術に関すること。 47 | ├── middleware // リクエスト毎に差し込む処理をまとめたミドルウェア 48 | ├── util 49 | └── testutil 50 | ``` 51 | 52 | ## 使い方 53 | 54 | ### ローカルでの起動方法 55 | 56 | ```bash 57 | cd server 58 | make deps 59 | make run 60 | ``` 61 | 62 | ### テスト 63 | 64 | ```bash 65 | cd server 66 | make test 67 | ``` 68 | 69 | ### 静的解析 70 | 71 | ```bash 72 | cd server 73 | make check 74 | ``` 75 | 76 | ## 参考文献 77 | 78 | ### サーバーサイド 79 | 80 | - InfoQ.com、徳武 聡(翻訳) (2009年6月7日) 『Domain Driven Design(ドメイン駆動設計) Quickly 日本語版』 InfoQ.com 81 | 82 | - エリック・エヴァンス(著)、今関 剛 (監修)、和智 右桂 (翻訳) (2011/4/9)『エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)』 翔泳社 83 | 84 | - pospome『pospomeのサーバサイドアーキテクチャ』 85 | 86 | ### フロントエンド 87 | 88 | - 花谷拓磨 (2018/10/17)『Nuxt.jsビギナーズガイド』シーアンドアール研究所 89 | 90 | - 川口 和也、喜多 啓介、野田 陽平、 手島 拓也、 片山 真也(2018/9/22)『Vue.js入門 基礎から実践アプリケーション開発まで』技術評論社 91 | 92 | 93 | 94 | 95 | ## 参考にさせていただいた記事 96 | 97 | - [Goを運用アプリケーションに導入する際のレイヤ構造模索の旅路 | Go Conference 2018 Autumn 発表レポート - BASE開発チームブログ](https://devblog.thebase.in/entry/2018/11/26/102401) 98 | 99 | - [ボトムアップドメイン駆動設計 │ nrslib](https://nrslib.com/bottomup-ddd/) 100 | 101 | - [GoでのAPI開発現場のアーキテクチャ実装事例 / go-api-architecture-practical-example - Speaker Deck](https://speakerdeck.com/hgsgtk/go-api-architecture-practical-example) 102 | 103 | - [GoのAPIのテストにおける共通処理 – timakin – Medium](https://medium.com/@timakin/go-api-testing-173b97fb23ec) 104 | 105 | - [CSSで作る!吹き出しデザインのサンプル19選](https://saruwakakun.com/html-css/reference/speech-bubble) 106 | - commentの吹き出し部分をかなり参考にさせていただいている 107 | -------------------------------------------------------------------------------- /server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/sekky0905/nuxt-vue-go-chat/server/application" 6 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/service" 7 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db" 8 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 9 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/router" 10 | "github.com/sekky0905/nuxt-vue-go-chat/server/interface/controller" 11 | "github.com/sekky0905/nuxt-vue-go-chat/server/middleware" 12 | ) 13 | 14 | func main() { 15 | apiV1 := router.G.Group("/v1") 16 | 17 | dbm := db.NewDBManager() 18 | ac := initializeAuthenticationController(dbm) 19 | ac.InitAuthenticationAPI(apiV1) 20 | 21 | threadRouting := apiV1.Group("/threads") 22 | 23 | // use middleware 24 | threadRouting.Use(middleware.CheckAuthentication()) 25 | 26 | cc := initializeCommentController(dbm) 27 | cc.InitCommentAPI(threadRouting) 28 | 29 | tc := initializeThreadController(dbm) 30 | tc.InitThreadAPI(threadRouting) 31 | 32 | router.G.NoRoute(func(g *gin.Context) { 33 | g.File("./../client/nuxt-vue-go-chat/dist/index.html") 34 | }) 35 | router.G.Static("/_nuxt", "./../client/nuxt-vue-go-chat/dist/_nuxt/") 36 | 37 | if err := router.G.Run(":8080"); err != nil { 38 | panic(err.Error()) 39 | } 40 | } 41 | 42 | // initializeAuthenticationController generates and returns AuthenticationController. 43 | func initializeAuthenticationController(m query.DBManager) controller.AuthenticationController { 44 | txCloser := db.CloseTransaction 45 | 46 | uRepo := db.NewUserRepository() 47 | sRepo := db.NewSessionRepository() 48 | uService := service.NewUserService(m, uRepo) 49 | sService := service.NewSessionService(sRepo) 50 | aService := service.NewAuthenticationService(uRepo) 51 | 52 | di := application.NewAuthenticationServiceDIInput(uRepo, sRepo, uService, sService, aService) 53 | aApp := application.NewAuthenticationService(m, di, txCloser) 54 | 55 | return controller.NewAuthenticationController(aApp) 56 | } 57 | 58 | // initializeThreadCController generates and returns ThreadCController. 59 | func initializeThreadController(m query.DBManager) controller.ThreadController { 60 | txCloser := db.CloseTransaction 61 | 62 | tRepo := db.NewThreadRepository() 63 | tService := service.NewThreadService(tRepo) 64 | 65 | tApp := application.NewThreadService(m, tService, tRepo, txCloser) 66 | 67 | return controller.NewThreadController(tApp) 68 | } 69 | 70 | // initializeCommentController generates and returns CommentController. 71 | func initializeCommentController(m query.DBManager) controller.CommentController { 72 | txCloser := db.CloseTransaction 73 | 74 | cRepo := db.NewCommentRepository() 75 | cService := service.NewCommentService(cRepo) 76 | 77 | cApp := application.NewCommentService(m, cService, cRepo, txCloser) 78 | 79 | return controller.NewCommentController(cApp) 80 | } 81 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/store/threads.js: -------------------------------------------------------------------------------- 1 | import { 2 | LIST_THREADS, 3 | SAVE_THREAD, 4 | EDIT_THREAD, 5 | DELETE_THREAD, 6 | LIST_THREADS_MORE, 7 | CHANGE_IS_DIALOG_VISIBLE 8 | } from './action-types' 9 | import { 10 | SET_THREADS, 11 | SET_THREAD_LIST, 12 | ADD_THREAD_LIST, 13 | ADD_THREAD, 14 | UPDATE_THREAD, 15 | REMOVE_THREAD, 16 | CLEAR_THREADS, 17 | SET_IS_DIALOG_VISIBLE 18 | } from './mutation-types' 19 | 20 | export const state = () => ({ 21 | threadList: { 22 | threads: [], 23 | hasNext: false, 24 | cursor: '' 25 | }, 26 | isDialogVisible: false 27 | }) 28 | 29 | export const getters = { 30 | threadList: state => state.threadList, 31 | threads: state => state.threadList.threads, 32 | isDialogVisible: state => state.isDialogVisible 33 | } 34 | 35 | export const mutations = { 36 | [SET_THREADS](state, { threads }) { 37 | state.threadList.threads = threads 38 | }, 39 | [SET_THREAD_LIST](state, { threadList }) { 40 | state.threadList = threadList 41 | }, 42 | [ADD_THREAD_LIST](state, { threadList }) { 43 | state.threadList.threads = state.threadList.threads.concat( 44 | threadList.threads 45 | ) 46 | state.threadList.hasNext = threadList.hasNext 47 | state.threadList.cursor = threadList.cursor 48 | }, 49 | [ADD_THREAD](state, { thread }) { 50 | state.threadList.threads.push(thread) 51 | }, 52 | [EDIT_THREAD](state, { thread }) { 53 | state.threadList.threads = state.threadList.threads.map(t => 54 | t.id === thread.id ? thread : t 55 | ) 56 | }, 57 | [REMOVE_THREAD](state, { id }) { 58 | state.threadList.threads = state.threadList.threads.filter(t => t.id !== id) 59 | }, 60 | [CLEAR_THREADS](state) { 61 | state.threadList = null 62 | }, 63 | [SET_IS_DIALOG_VISIBLE](state, { dialogState }) { 64 | state.isDialogVisible = !dialogState 65 | } 66 | } 67 | 68 | export const actions = { 69 | async [LIST_THREADS]({ commit }) { 70 | const list = await this.$axios.$get('/threads?limit=20') 71 | commit(SET_THREAD_LIST, { threadList: list }) 72 | }, 73 | async [LIST_THREADS_MORE]({ commit }, { limit, cursor }) { 74 | const list = await this.$axios.$get( 75 | `/threads?limit=${limit}&cursor=${cursor}` 76 | ) 77 | 78 | commit(ADD_THREAD_LIST, { threadList: list }) 79 | }, 80 | async [SAVE_THREAD]({ commit }, { payload }) { 81 | const response = await this.$axios.$post(`/threads`, payload) 82 | commit(ADD_THREAD, { thread: response }) 83 | }, 84 | 85 | async [EDIT_THREAD]({ commit }, { payload }) { 86 | const response = await this.$axios.$put(`/threads/${payload.id}`, payload) 87 | commit(UPDATE_THREAD, { thread: response }) 88 | }, 89 | 90 | async [DELETE_THREAD]({ commit }, { id }) { 91 | await this.$axios.$delete(`/threads/${id}`) 92 | commit(REMOVE_THREAD, id) 93 | }, 94 | [CHANGE_IS_DIALOG_VISIBLE]({ commit }, { dialogState }) { 95 | commit(SET_IS_DIALOG_VISIBLE, { dialogState: dialogState }) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /server/domain/service/mock/session.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: server/domain/service/session.go 3 | 4 | // Package mock_service is a generated GoMock package. 5 | package mock_service 6 | 7 | import ( 8 | context "context" 9 | gomock "github.com/golang/mock/gomock" 10 | model "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 11 | query "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 12 | reflect "reflect" 13 | ) 14 | 15 | // MockSessionService is a mock of SessionService interface 16 | type MockSessionService struct { 17 | ctrl *gomock.Controller 18 | recorder *MockSessionServiceMockRecorder 19 | } 20 | 21 | // MockSessionServiceMockRecorder is the mock recorder for MockSessionService 22 | type MockSessionServiceMockRecorder struct { 23 | mock *MockSessionService 24 | } 25 | 26 | // NewMockSessionService creates a new mock instance 27 | func NewMockSessionService(ctrl *gomock.Controller) *MockSessionService { 28 | mock := &MockSessionService{ctrl: ctrl} 29 | mock.recorder = &MockSessionServiceMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use 34 | func (m *MockSessionService) EXPECT() *MockSessionServiceMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // NewSession mocks base method 39 | func (m *MockSessionService) NewSession(userID uint32) *model.Session { 40 | m.ctrl.T.Helper() 41 | ret := m.ctrl.Call(m, "NewSession", userID) 42 | ret0, _ := ret[0].(*model.Session) 43 | return ret0 44 | } 45 | 46 | // NewSession indicates an expected call of NewSession 47 | func (mr *MockSessionServiceMockRecorder) NewSession(userID interface{}) *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewSession", reflect.TypeOf((*MockSessionService)(nil).NewSession), userID) 50 | } 51 | 52 | // SessionID mocks base method 53 | func (m *MockSessionService) SessionID() string { 54 | m.ctrl.T.Helper() 55 | ret := m.ctrl.Call(m, "SessionID") 56 | ret0, _ := ret[0].(string) 57 | return ret0 58 | } 59 | 60 | // SessionID indicates an expected call of SessionID 61 | func (mr *MockSessionServiceMockRecorder) SessionID() *gomock.Call { 62 | mr.mock.ctrl.T.Helper() 63 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SessionID", reflect.TypeOf((*MockSessionService)(nil).SessionID)) 64 | } 65 | 66 | // IsAlreadyExistID mocks base method 67 | func (m_2 *MockSessionService) IsAlreadyExistID(ctx context.Context, m query.SQLManager, id string) (bool, error) { 68 | m_2.ctrl.T.Helper() 69 | ret := m_2.ctrl.Call(m_2, "IsAlreadyExistID", ctx, m, id) 70 | ret0, _ := ret[0].(bool) 71 | ret1, _ := ret[1].(error) 72 | return ret0, ret1 73 | } 74 | 75 | // IsAlreadyExistID indicates an expected call of IsAlreadyExistID 76 | func (mr *MockSessionServiceMockRecorder) IsAlreadyExistID(ctx, m, id interface{}) *gomock.Call { 77 | mr.mock.ctrl.T.Helper() 78 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAlreadyExistID", reflect.TypeOf((*MockSessionService)(nil).IsAlreadyExistID), ctx, m, id) 79 | } 80 | -------------------------------------------------------------------------------- /server/application/mock/authentication.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: server/application/authentication.go 3 | 4 | // Package mock_application is a generated GoMock package. 5 | package mock_application 6 | 7 | import ( 8 | context "context" 9 | gomock "github.com/golang/mock/gomock" 10 | model "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 11 | reflect "reflect" 12 | ) 13 | 14 | // MockAuthenticationService is a mock of AuthenticationService interface 15 | type MockAuthenticationService struct { 16 | ctrl *gomock.Controller 17 | recorder *MockAuthenticationServiceMockRecorder 18 | } 19 | 20 | // MockAuthenticationServiceMockRecorder is the mock recorder for MockAuthenticationService 21 | type MockAuthenticationServiceMockRecorder struct { 22 | mock *MockAuthenticationService 23 | } 24 | 25 | // NewMockAuthenticationService creates a new mock instance 26 | func NewMockAuthenticationService(ctrl *gomock.Controller) *MockAuthenticationService { 27 | mock := &MockAuthenticationService{ctrl: ctrl} 28 | mock.recorder = &MockAuthenticationServiceMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use 33 | func (m *MockAuthenticationService) EXPECT() *MockAuthenticationServiceMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // SignUp mocks base method 38 | func (m *MockAuthenticationService) SignUp(ctx context.Context, param *model.User) (*model.User, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "SignUp", ctx, param) 41 | ret0, _ := ret[0].(*model.User) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // SignUp indicates an expected call of SignUp 47 | func (mr *MockAuthenticationServiceMockRecorder) SignUp(ctx, param interface{}) *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignUp", reflect.TypeOf((*MockAuthenticationService)(nil).SignUp), ctx, param) 50 | } 51 | 52 | // Login mocks base method 53 | func (m *MockAuthenticationService) Login(ctx context.Context, param *model.User) (*model.User, error) { 54 | m.ctrl.T.Helper() 55 | ret := m.ctrl.Call(m, "Login", ctx, param) 56 | ret0, _ := ret[0].(*model.User) 57 | ret1, _ := ret[1].(error) 58 | return ret0, ret1 59 | } 60 | 61 | // Login indicates an expected call of Login 62 | func (mr *MockAuthenticationServiceMockRecorder) Login(ctx, param interface{}) *gomock.Call { 63 | mr.mock.ctrl.T.Helper() 64 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockAuthenticationService)(nil).Login), ctx, param) 65 | } 66 | 67 | // Logout mocks base method 68 | func (m *MockAuthenticationService) Logout(ctx context.Context, sessionID string) error { 69 | m.ctrl.T.Helper() 70 | ret := m.ctrl.Call(m, "Logout", ctx, sessionID) 71 | ret0, _ := ret[0].(error) 72 | return ret0 73 | } 74 | 75 | // Logout indicates an expected call of Logout 76 | func (mr *MockAuthenticationServiceMockRecorder) Logout(ctx, sessionID interface{}) *gomock.Call { 77 | mr.mock.ctrl.T.Helper() 78 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockAuthenticationService)(nil).Logout), ctx, sessionID) 79 | } 80 | -------------------------------------------------------------------------------- /server/domain/service/mock/user.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: server/domain/service/user.go 3 | 4 | // Package mock_service is a generated GoMock package. 5 | package mock_service 6 | 7 | import ( 8 | context "context" 9 | gomock "github.com/golang/mock/gomock" 10 | model "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 11 | query "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 12 | reflect "reflect" 13 | ) 14 | 15 | // MockUserService is a mock of UserService interface 16 | type MockUserService struct { 17 | ctrl *gomock.Controller 18 | recorder *MockUserServiceMockRecorder 19 | } 20 | 21 | // MockUserServiceMockRecorder is the mock recorder for MockUserService 22 | type MockUserServiceMockRecorder struct { 23 | mock *MockUserService 24 | } 25 | 26 | // NewMockUserService creates a new mock instance 27 | func NewMockUserService(ctrl *gomock.Controller) *MockUserService { 28 | mock := &MockUserService{ctrl: ctrl} 29 | mock.recorder = &MockUserServiceMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use 34 | func (m *MockUserService) EXPECT() *MockUserServiceMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // NewUser mocks base method 39 | func (m *MockUserService) NewUser(name, password string) (*model.User, error) { 40 | m.ctrl.T.Helper() 41 | ret := m.ctrl.Call(m, "NewUser", name, password) 42 | ret0, _ := ret[0].(*model.User) 43 | ret1, _ := ret[1].(error) 44 | return ret0, ret1 45 | } 46 | 47 | // NewUser indicates an expected call of NewUser 48 | func (mr *MockUserServiceMockRecorder) NewUser(name, password interface{}) *gomock.Call { 49 | mr.mock.ctrl.T.Helper() 50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewUser", reflect.TypeOf((*MockUserService)(nil).NewUser), name, password) 51 | } 52 | 53 | // IsAlreadyExistID mocks base method 54 | func (m_2 *MockUserService) IsAlreadyExistID(ctx context.Context, m query.SQLManager, id uint32) (bool, error) { 55 | m_2.ctrl.T.Helper() 56 | ret := m_2.ctrl.Call(m_2, "IsAlreadyExistID", ctx, m, id) 57 | ret0, _ := ret[0].(bool) 58 | ret1, _ := ret[1].(error) 59 | return ret0, ret1 60 | } 61 | 62 | // IsAlreadyExistID indicates an expected call of IsAlreadyExistID 63 | func (mr *MockUserServiceMockRecorder) IsAlreadyExistID(ctx, m, id interface{}) *gomock.Call { 64 | mr.mock.ctrl.T.Helper() 65 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAlreadyExistID", reflect.TypeOf((*MockUserService)(nil).IsAlreadyExistID), ctx, m, id) 66 | } 67 | 68 | // IsAlreadyExistName mocks base method 69 | func (m_2 *MockUserService) IsAlreadyExistName(ctx context.Context, m query.SQLManager, name string) (bool, error) { 70 | m_2.ctrl.T.Helper() 71 | ret := m_2.ctrl.Call(m_2, "IsAlreadyExistName", ctx, m, name) 72 | ret0, _ := ret[0].(bool) 73 | ret1, _ := ret[1].(error) 74 | return ret0, ret1 75 | } 76 | 77 | // IsAlreadyExistName indicates an expected call of IsAlreadyExistName 78 | func (mr *MockUserServiceMockRecorder) IsAlreadyExistName(ctx, m, name interface{}) *gomock.Call { 79 | mr.mock.ctrl.T.Helper() 80 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAlreadyExistName", reflect.TypeOf((*MockUserService)(nil).IsAlreadyExistName), ctx, m, name) 81 | } 82 | -------------------------------------------------------------------------------- /server/interface/controller/authentication.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/pkg/errors" 8 | "github.com/sekky0905/nuxt-vue-go-chat/server/application" 9 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 10 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/logger" 11 | ) 12 | 13 | // AuthenticationController is the interface of AuthenticationController. 14 | type AuthenticationController interface { 15 | InitAuthenticationAPI(g *gin.RouterGroup) 16 | SignUp(g *gin.Context) 17 | Login(g *gin.Context) 18 | Logout(g *gin.Context) 19 | } 20 | 21 | // authenticationController is the controller of authentication. 22 | type authenticationController struct { 23 | aApp application.AuthenticationService 24 | } 25 | 26 | // NewAuthenticationController generates and returns AuthenticationController. 27 | func NewAuthenticationController(uAPP application.AuthenticationService) AuthenticationController { 28 | return &authenticationController{ 29 | aApp: uAPP, 30 | } 31 | } 32 | 33 | // InitAuthenticationAPI initialize Authentication API. 34 | func (c *authenticationController) InitAuthenticationAPI(g *gin.RouterGroup) { 35 | g.POST("/signUp", c.SignUp) 36 | g.POST("/login", c.Login) 37 | g.DELETE("/logout", c.Logout) 38 | } 39 | 40 | // SignUp sign up an user. 41 | func (c *authenticationController) SignUp(g *gin.Context) { 42 | param := &model.User{} 43 | if err := g.BindJSON(param); err != nil { 44 | err = handleValidatorErr(err) 45 | ResponseAndLogError(g, errors.Wrap(err, "failed to bind json")) 46 | return 47 | } 48 | 49 | ctx := g.Request.Context() 50 | user, err := c.aApp.SignUp(ctx, param) 51 | if err != nil { 52 | ResponseAndLogError(g, errors.Wrap(err, "failed to sign up")) 53 | return 54 | } 55 | 56 | g.SetCookie(model.SessionIDAtCookie, user.SessionID, 86400, "/", "", false, true) 57 | 58 | uDTO := TranslateFromUserToUserDTO(user) 59 | g.JSON(http.StatusOK, uDTO) 60 | } 61 | 62 | // Login login an user. 63 | func (c *authenticationController) Login(g *gin.Context) { 64 | param := &model.User{} 65 | if err := g.BindJSON(param); err != nil { 66 | err = handleValidatorErr(err) 67 | ResponseAndLogError(g, errors.Wrap(err, "failed to bind json")) 68 | return 69 | } 70 | 71 | ctx := g.Request.Context() 72 | user, err := c.aApp.Login(ctx, param) 73 | if err != nil { 74 | ResponseAndLogError(g, errors.Wrap(err, "failed to sign up")) 75 | return 76 | } 77 | 78 | g.SetCookie(model.SessionIDAtCookie, user.SessionID, 86400, "/", "", false, true) 79 | 80 | uDTO := TranslateFromUserToUserDTO(user) 81 | g.JSON(http.StatusOK, uDTO) 82 | } 83 | 84 | // Logout logout an user. 85 | func (c *authenticationController) Logout(g *gin.Context) { 86 | sessionID, err := g.Cookie(model.SessionIDAtCookie) 87 | if err != nil && err != http.ErrNoCookie { 88 | logger.Logger.Warn("failed to read session from Cookie") 89 | return 90 | } 91 | 92 | ctx := g.Request.Context() 93 | if err = c.aApp.Logout(ctx, sessionID); err != nil { 94 | ResponseAndLogError(g, errors.Wrap(err, "failed to logout")) 95 | return 96 | } 97 | 98 | // empty cookie 99 | g.SetCookie(model.SessionIDAtCookie, "", 0, "", "", false, true) 100 | 101 | g.JSON(http.StatusOK, nil) 102 | } 103 | -------------------------------------------------------------------------------- /server/domain/repository/mock/session.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: server/domain/repository/session.go 3 | 4 | // Package mock_repository is a generated GoMock package. 5 | package mock_repository 6 | 7 | import ( 8 | context "context" 9 | gomock "github.com/golang/mock/gomock" 10 | model "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 11 | query "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 12 | reflect "reflect" 13 | ) 14 | 15 | // MockSessionRepository is a mock of SessionRepository interface 16 | type MockSessionRepository struct { 17 | ctrl *gomock.Controller 18 | recorder *MockSessionRepositoryMockRecorder 19 | } 20 | 21 | // MockSessionRepositoryMockRecorder is the mock recorder for MockSessionRepository 22 | type MockSessionRepositoryMockRecorder struct { 23 | mock *MockSessionRepository 24 | } 25 | 26 | // NewMockSessionRepository creates a new mock instance 27 | func NewMockSessionRepository(ctrl *gomock.Controller) *MockSessionRepository { 28 | mock := &MockSessionRepository{ctrl: ctrl} 29 | mock.recorder = &MockSessionRepositoryMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use 34 | func (m *MockSessionRepository) EXPECT() *MockSessionRepositoryMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // GetSessionByID mocks base method 39 | func (m_2 *MockSessionRepository) GetSessionByID(ctx context.Context, m query.SQLManager, id string) (*model.Session, error) { 40 | m_2.ctrl.T.Helper() 41 | ret := m_2.ctrl.Call(m_2, "GetSessionByID", ctx, m, id) 42 | ret0, _ := ret[0].(*model.Session) 43 | ret1, _ := ret[1].(error) 44 | return ret0, ret1 45 | } 46 | 47 | // GetSessionByID indicates an expected call of GetSessionByID 48 | func (mr *MockSessionRepositoryMockRecorder) GetSessionByID(ctx, m, id interface{}) *gomock.Call { 49 | mr.mock.ctrl.T.Helper() 50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSessionByID", reflect.TypeOf((*MockSessionRepository)(nil).GetSessionByID), ctx, m, id) 51 | } 52 | 53 | // InsertSession mocks base method 54 | func (m_2 *MockSessionRepository) InsertSession(ctx context.Context, m query.SQLManager, session *model.Session) error { 55 | m_2.ctrl.T.Helper() 56 | ret := m_2.ctrl.Call(m_2, "InsertSession", ctx, m, session) 57 | ret0, _ := ret[0].(error) 58 | return ret0 59 | } 60 | 61 | // InsertSession indicates an expected call of InsertSession 62 | func (mr *MockSessionRepositoryMockRecorder) InsertSession(ctx, m, session interface{}) *gomock.Call { 63 | mr.mock.ctrl.T.Helper() 64 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertSession", reflect.TypeOf((*MockSessionRepository)(nil).InsertSession), ctx, m, session) 65 | } 66 | 67 | // DeleteSession mocks base method 68 | func (m_2 *MockSessionRepository) DeleteSession(ctx context.Context, m query.SQLManager, id string) error { 69 | m_2.ctrl.T.Helper() 70 | ret := m_2.ctrl.Call(m_2, "DeleteSession", ctx, m, id) 71 | ret0, _ := ret[0].(error) 72 | return ret0 73 | } 74 | 75 | // DeleteSession indicates an expected call of DeleteSession 76 | func (mr *MockSessionRepositoryMockRecorder) DeleteSession(ctx, m, id interface{}) *gomock.Call { 77 | mr.mock.ctrl.T.Helper() 78 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSession", reflect.TypeOf((*MockSessionRepository)(nil).DeleteSession), ctx, m, id) 79 | } 80 | -------------------------------------------------------------------------------- /server/domain/service/session_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/golang/mock/gomock" 8 | "github.com/pkg/errors" 9 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 10 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/repository" 11 | mock_repository "github.com/sekky0905/nuxt-vue-go-chat/server/domain/repository/mock" 12 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db" 13 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 14 | "github.com/sekky0905/nuxt-vue-go-chat/server/testutil" 15 | ) 16 | 17 | func Test_sessionService_IsAlreadyExistID(t *testing.T) { 18 | // for gomock 19 | ctrl := gomock.NewController(t) 20 | defer ctrl.Finish() 21 | 22 | mock := mock_repository.NewMockSessionRepository(ctrl) 23 | 24 | type fields struct { 25 | repo repository.SessionRepository 26 | } 27 | type args struct { 28 | ctx context.Context 29 | m query.SQLManager 30 | id string 31 | } 32 | 33 | type returnArgs struct { 34 | session *model.Session 35 | err error 36 | } 37 | 38 | tests := []struct { 39 | name string 40 | fields fields 41 | args args 42 | returnArgs 43 | want bool 44 | wantErr error 45 | }{ 46 | { 47 | name: "When specified session already exists, return true and nil.", 48 | fields: fields{ 49 | repo: mock, 50 | }, 51 | args: args{ 52 | ctx: context.Background(), 53 | m: db.NewDBManager(), 54 | id: model.SessionValidIDForTest, 55 | }, 56 | returnArgs: returnArgs{ 57 | session: &model.Session{ 58 | ID: model.SessionValidIDForTest, 59 | UserID: model.UserValidIDForTest, 60 | CreatedAt: testutil.TimeNow(), 61 | }, 62 | err: nil, 63 | }, 64 | want: true, 65 | wantErr: nil, 66 | }, 67 | { 68 | name: "When specified session doesn't already exists, return true and nil.", 69 | fields: fields{ 70 | repo: mock, 71 | }, 72 | args: args{ 73 | ctx: context.Background(), 74 | id: model.SessionInValidIDForTest, 75 | }, 76 | returnArgs: returnArgs{ 77 | session: nil, 78 | err: nil, 79 | }, 80 | want: false, 81 | wantErr: nil, 82 | }, 83 | { 84 | name: "When some error has occurred, return false and error.", 85 | fields: fields{ 86 | repo: mock, 87 | }, 88 | args: args{ 89 | ctx: context.Background(), 90 | id: model.SessionInValidIDForTest, 91 | }, 92 | returnArgs: returnArgs{ 93 | session: nil, 94 | err: errors.New(model.ErrorMessageForTest), 95 | }, 96 | want: false, 97 | wantErr: errors.New(model.ErrorMessageForTest), 98 | }, 99 | } 100 | for _, tt := range tests { 101 | t.Run(tt.name, func(t *testing.T) { 102 | s := &sessionService{ 103 | repo: mock, 104 | } 105 | 106 | mock.EXPECT().GetSessionByID(tt.args.ctx, tt.args.m, tt.args.id).Return(tt.returnArgs.session, tt.returnArgs.err) 107 | 108 | got, err := s.IsAlreadyExistID(tt.args.ctx, tt.args.m, tt.args.id) 109 | if tt.wantErr != nil { 110 | if errors.Cause(err).Error() != tt.wantErr.Error() { 111 | t.Errorf("sessionService.IsAlreadyExistID() error = %v, wantErr %v", err, tt.wantErr) 112 | return 113 | } 114 | } 115 | if got != tt.want { 116 | t.Errorf("sessionService.IsAlreadyExistID() = %v, want %v", got, tt.want) 117 | } 118 | }) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /server/domain/service/comment_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/golang/mock/gomock" 8 | "github.com/pkg/errors" 9 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 10 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/repository" 11 | mock_repository "github.com/sekky0905/nuxt-vue-go-chat/server/domain/repository/mock" 12 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db" 13 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 14 | ) 15 | 16 | func Test_commentService_IsAlreadyExistID(t *testing.T) { 17 | // for gomock 18 | ctrl := gomock.NewController(t) 19 | defer ctrl.Finish() 20 | 21 | mock := mock_repository.NewMockCommentRepository(ctrl) 22 | 23 | type fields struct { 24 | repo repository.CommentRepository 25 | } 26 | type args struct { 27 | ctx context.Context 28 | m query.SQLManager 29 | id uint32 30 | } 31 | 32 | type returnArgs struct { 33 | comment *model.Comment 34 | err error 35 | } 36 | 37 | tests := []struct { 38 | name string 39 | fields fields 40 | args args 41 | returnArgs 42 | want bool 43 | wantErr error 44 | }{ 45 | { 46 | name: "When specified comment already exists, return true and nil.", 47 | fields: fields{ 48 | repo: mock, 49 | }, 50 | args: args{ 51 | ctx: context.Background(), 52 | m: db.NewDBManager(), 53 | id: model.CommentValidIDForTest, 54 | }, 55 | returnArgs: returnArgs{ 56 | comment: &model.Comment{ 57 | ID: model.CommentInValidIDForTest, 58 | ThreadID: model.ThreadValidIDForTest, 59 | User: &model.User{ 60 | ID: model.UserValidIDForTest, 61 | Name: model.UserNameForTest, 62 | }, 63 | Content: model.CommentContentForTest, 64 | }, 65 | err: nil, 66 | }, 67 | want: true, 68 | wantErr: nil, 69 | }, 70 | { 71 | name: "When specified comment doesn't already exists, return true and nil.", 72 | fields: fields{ 73 | repo: mock, 74 | }, 75 | args: args{ 76 | ctx: context.Background(), 77 | m: db.NewDBManager(), 78 | id: model.CommentInValidIDForTest, 79 | }, 80 | returnArgs: returnArgs{ 81 | comment: nil, 82 | err: nil, 83 | }, 84 | want: false, 85 | wantErr: nil, 86 | }, 87 | { 88 | name: "When some error has occurred, return false and error.", 89 | fields: fields{ 90 | repo: mock, 91 | }, 92 | args: args{ 93 | ctx: context.Background(), 94 | m: db.NewDBManager(), 95 | id: model.CommentInValidIDForTest, 96 | }, 97 | returnArgs: returnArgs{ 98 | comment: nil, 99 | err: errors.New(model.ErrorMessageForTest), 100 | }, 101 | want: false, 102 | wantErr: errors.New(model.ErrorMessageForTest), 103 | }, 104 | } 105 | for _, tt := range tests { 106 | t.Run(tt.name, func(t *testing.T) { 107 | s := &commentService{ 108 | repo: mock, 109 | } 110 | 111 | mock.EXPECT().GetCommentByID(tt.args.ctx, tt.args.m, tt.args.id).Return(tt.returnArgs.comment, tt.returnArgs.err) 112 | 113 | got, err := s.IsAlreadyExistID(tt.args.ctx, tt.args.m, tt.args.id) 114 | if tt.wantErr != nil { 115 | if errors.Cause(err).Error() != tt.wantErr.Error() { 116 | t.Errorf("commentService.IsAlreadyExistID() error = %v, wantErr %v", err, tt.wantErr) 117 | return 118 | } 119 | } 120 | if got != tt.want { 121 | t.Errorf("commentService.IsAlreadyExistID() = %v, want %v", got, tt.want) 122 | } 123 | }) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/store/comments.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_COMMENTS, 3 | SET_COMMENT_LIST, 4 | ADD_COMMENT_LIST, 5 | ADD_COMMENT, 6 | UPDATE_COMMENT, 7 | REMOVE_COMMENT, 8 | CLEAR_COMMENTS, 9 | SET_IS_DIALOG_VISIBLE 10 | } from './mutation-types' 11 | 12 | import { 13 | LIST_COMMENTS, 14 | LIST_COMMENTS_MORE, 15 | SAVE_COMMENT, 16 | EDIT_COMMENT, 17 | DELETE_COMMENT, 18 | CHANGE_IS_DIALOG_VISIBLE 19 | } from './action-types' 20 | 21 | export const state = () => ({ 22 | commentList: { 23 | comments: [], 24 | hasNext: false, 25 | cursor: '' 26 | }, 27 | isDialogVisible: false 28 | }) 29 | 30 | export const getters = { 31 | commentList: state => state.commentList, 32 | comments: state => state.commentList.comments, 33 | isDialogVisible: state => state.isDialogVisible 34 | } 35 | 36 | export const mutations = { 37 | [SET_COMMENTS](state, { comments }) { 38 | state.commentList.comments = comments 39 | }, 40 | [SET_COMMENT_LIST](state, { commentList }) { 41 | state.commentList = commentList 42 | }, 43 | [ADD_COMMENT_LIST](state, { commentList }) { 44 | state.commentList.comments = state.commentList.comments.concat( 45 | commentList.comments 46 | ) 47 | state.commentList.hasNext = commentList.hasNext 48 | state.commentList.cursor = commentList.cursor 49 | }, 50 | [ADD_COMMENT](state, { comment }) { 51 | state.commentList.comments.push(comment) 52 | }, 53 | [UPDATE_COMMENT](state, { comment }) { 54 | state.commentList.comments = state.commentList.comments.map(t => 55 | t.id === comment.id ? comment : t 56 | ) 57 | }, 58 | [REMOVE_COMMENT](state, { id }) { 59 | state.commentList.comments = state.commentList.comments.filter(t => { 60 | return t.id !== id 61 | }) 62 | }, 63 | [CLEAR_COMMENTS](state) { 64 | state.commentList = { 65 | comments: [], 66 | hasNext: false, 67 | cursor: '' 68 | } 69 | }, 70 | [SET_IS_DIALOG_VISIBLE](state, { dialogState }) { 71 | state.isDialogVisible = !dialogState 72 | } 73 | } 74 | 75 | export const actions = { 76 | async [LIST_COMMENTS]({ commit }, { threadId }) { 77 | commit(CLEAR_COMMENTS) 78 | const list = await this.$axios.$get(`threads/${threadId}/comments`) 79 | if (!list.comments) { 80 | return 81 | } 82 | commit(SET_COMMENT_LIST, { commentList: list }) 83 | }, 84 | async [LIST_COMMENTS_MORE]({ commit }, { threadId, limit, cursor }) { 85 | const list = await this.$axios.$get( 86 | `/threads/${threadId}/comments?limit=${limit}&cursor=${cursor}` 87 | ) 88 | if (!list.comments) { 89 | return 90 | } 91 | commit(ADD_COMMENT_LIST, { commentList: list }) 92 | }, 93 | async [SAVE_COMMENT]({ commit }, { payload }) { 94 | const response = await this.$axios.$post( 95 | `threads/${payload.threadId}/comments`, 96 | payload 97 | ) 98 | commit(ADD_COMMENT, { comment: response }) 99 | }, 100 | async [EDIT_COMMENT]({ commit }, { payload }) { 101 | const response = await this.$axios.$put( 102 | `threads/${payload.threadId}/comments/${payload.id}`, 103 | payload 104 | ) 105 | commit(UPDATE_COMMENT, { comment: response }) 106 | }, 107 | 108 | async [DELETE_COMMENT]({ commit }, { threadId, id }) { 109 | await this.$axios.$delete(`threads/${threadId}/comments/${id}`) 110 | commit(REMOVE_COMMENT, { id: id }) 111 | }, 112 | 113 | [CHANGE_IS_DIALOG_VISIBLE]({ commit }, { dialogState }) { 114 | commit(SET_IS_DIALOG_VISIBLE, { dialogState: dialogState }) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/components/ThreadsInput.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 117 | -------------------------------------------------------------------------------- /server/domain/model/errrors.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // RepositoryMethod is method of Repository. 9 | type RepositoryMethod string 10 | 11 | // methods of Repository. 12 | const ( 13 | RepositoryMethodREAD RepositoryMethod = "READ" 14 | RepositoryMethodInsert RepositoryMethod = "INSERT" 15 | RepositoryMethodUPDATE RepositoryMethod = "UPDATE" 16 | RepositoryMethodDELETE RepositoryMethod = "DELETE" 17 | RepositoryMethodLIST RepositoryMethod = "LIST" 18 | ) 19 | 20 | // InvalidDataError expresses that given data is invalid. 21 | type InvalidDataError struct { 22 | BaseErr error 23 | DataName string 24 | DataValue interface{} 25 | InvalidReason string 26 | } 27 | 28 | // Error returns error message. 29 | func (e *InvalidDataError) Error() string { 30 | return fmt.Sprintf("%s, %s", e.DataName, e.InvalidReason) 31 | } 32 | 33 | // AlreadyExistError expresses already specified data has existed. 34 | type AlreadyExistError struct { 35 | BaseErr error 36 | PropertyName 37 | PropertyValue interface{} 38 | DomainModelName 39 | } 40 | 41 | // Error returns error message. 42 | func (e *AlreadyExistError) Error() string { 43 | return fmt.Sprintf("%s, %s, is already exists", e.PropertyName, e.DomainModelName) 44 | } 45 | 46 | // RequiredError is not existing necessary value error. 47 | type RequiredError struct { 48 | BaseErr error 49 | PropertyName 50 | } 51 | 52 | // Error returns error message. 53 | func (e *RequiredError) Error() string { 54 | return fmt.Sprintf("%s is required", e.PropertyName) 55 | } 56 | 57 | // InvalidParamError is inappropriate parameter error。 58 | type InvalidParamError struct { 59 | BaseErr error 60 | PropertyName 61 | PropertyValue interface{} 62 | InvalidReason string 63 | } 64 | 65 | // Error returns error message. 66 | func (e *InvalidParamError) Error() string { 67 | return fmt.Sprintf("%s, %v, is invalid, %s", e.PropertyName, e.PropertyValue, e.InvalidReason) 68 | } 69 | 70 | // InvalidParamsError is inappropriate parameters error。 71 | type InvalidParamsError struct { 72 | Errors []*InvalidParamError 73 | } 74 | 75 | // Error returns error message. 76 | func (e *InvalidParamsError) Error() string { 77 | length := len(e.Errors) 78 | messages := make([]string, length, length) 79 | for i, err := range e.Errors { 80 | messages[i] = err.Error() 81 | } 82 | return strings.Join(messages, ",") 83 | } 84 | 85 | // NoSuchDataError is not existing specified data error. 86 | type NoSuchDataError struct { 87 | BaseErr error 88 | PropertyName 89 | PropertyValue interface{} 90 | DomainModelName 91 | } 92 | 93 | // Error returns error message. 94 | func (e *NoSuchDataError) Error() string { 95 | return fmt.Sprintf("no such data, %s: %v, %s", e.PropertyName, e.PropertyValue, e.DomainModelName) 96 | } 97 | 98 | // RepositoryError is Repository error. 99 | type RepositoryError struct { 100 | BaseErr error 101 | RepositoryMethod RepositoryMethod 102 | DomainModelName 103 | } 104 | 105 | // Error returns error message. 106 | func (e *RepositoryError) Error() string { 107 | return fmt.Sprintf("failed Repository operation, %s, %s", e.RepositoryMethod, e.DomainModelName) 108 | } 109 | 110 | // SQLError is SQL error. 111 | type SQLError struct { 112 | BaseErr error 113 | InvalidReasonForDeveloper InvalidReason 114 | } 115 | 116 | // Error returns error message. 117 | func (e *SQLError) Error() string { 118 | return e.InvalidReasonForDeveloper.String() 119 | } 120 | 121 | // AuthenticationErr is Authentication error. 122 | type AuthenticationErr struct { 123 | BaseErr error 124 | } 125 | 126 | // Error returns error message. 127 | func (e *AuthenticationErr) Error() string { 128 | return "invalid name or password" 129 | } 130 | 131 | // OtherServerError is other server error. 132 | type OtherServerError struct { 133 | BaseErr error 134 | InvalidReason string 135 | } 136 | 137 | // Error returns error message. 138 | func (e *OtherServerError) Error() string { 139 | return e.InvalidReason 140 | } 141 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/components/CommentInput.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 126 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/components/Login.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /server/interface/controller/errors.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/logger" 8 | 9 | "github.com/pkg/errors" 10 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 11 | ) 12 | 13 | // handledError is the handled error. 14 | type handledError struct { 15 | BaseError error `json:"-"` 16 | Status int `json:"-"` 17 | Code ErrCode `json:"code"` 18 | Message string `json:"message"` 19 | } 20 | 21 | const systemError = "system error has occurred" 22 | 23 | // handleError handles error. 24 | // This generates and returns status code and handledError. 25 | func handleError(err error) *handledError { 26 | switch errors.Cause(err).(type) { 27 | case *model.NoSuchDataError: 28 | realErr, ok := errors.Cause(err).(*model.NoSuchDataError) 29 | if !ok { 30 | logger.Logger.Error(fmt.Sprintf("failed to assert. err = %+v", err)) 31 | return nil 32 | } 33 | 34 | return &handledError{ 35 | BaseError: realErr.BaseErr, 36 | Status: http.StatusNotFound, 37 | Code: NoSuchDataFailure, 38 | Message: errors.Cause(err).Error(), 39 | } 40 | case *model.RequiredError: 41 | realErr, ok := errors.Cause(err).(*model.RequiredError) 42 | if !ok { 43 | logger.Logger.Error(fmt.Sprintf("failed to assert. err = %+v", err)) 44 | return nil 45 | } 46 | 47 | return &handledError{ 48 | BaseError: realErr.BaseErr, 49 | Status: http.StatusBadRequest, 50 | Code: RequiredFailure, 51 | Message: errors.Cause(err).Error(), 52 | } 53 | case *model.InvalidParamError: 54 | realErr, ok := errors.Cause(err).(*model.InvalidParamError) 55 | if !ok { 56 | logger.Logger.Error(fmt.Sprintf("failed to assert. err = %+v", err)) 57 | return nil 58 | } 59 | 60 | return &handledError{ 61 | BaseError: realErr.BaseErr, 62 | Status: http.StatusBadRequest, 63 | Code: InvalidParameterValueFailure, 64 | Message: errors.Cause(err).Error(), 65 | } 66 | case *model.InvalidParamsError: 67 | return &handledError{ 68 | Status: http.StatusBadRequest, 69 | Code: InvalidParametersValueFailure, 70 | Message: errors.Cause(err).Error(), 71 | } 72 | case *model.AlreadyExistError: 73 | realErr, ok := errors.Cause(err).(*model.AlreadyExistError) 74 | if !ok { 75 | logger.Logger.Error(fmt.Sprintf("failed to assert. err = %+v", err)) 76 | return nil 77 | } 78 | 79 | return &handledError{ 80 | BaseError: realErr.BaseErr, 81 | Status: http.StatusConflict, 82 | Code: AlreadyExistsFailure, 83 | Message: errors.Cause(err).Error(), 84 | } 85 | case *model.AuthenticationErr: 86 | return &handledError{ 87 | Status: http.StatusUnauthorized, 88 | Code: AuthenticationFailure, 89 | Message: errors.Cause(err).Error(), 90 | } 91 | case *model.RepositoryError: 92 | realErr, ok := errors.Cause(err).(*model.RepositoryError) 93 | if !ok { 94 | logger.Logger.Error(fmt.Sprintf("failed to assert. err = %+v", err)) 95 | return nil 96 | } 97 | 98 | return &handledError{ 99 | BaseError: realErr.BaseErr, 100 | Status: http.StatusInternalServerError, 101 | Code: InternalDBFailure, 102 | Message: systemError, 103 | } 104 | case *model.SQLError: 105 | realErr, ok := errors.Cause(err).(*model.SQLError) 106 | if !ok { 107 | logger.Logger.Error(fmt.Sprintf("failed to assert. err = %+v", err)) 108 | return nil 109 | } 110 | 111 | return &handledError{ 112 | BaseError: realErr.BaseErr, 113 | Status: http.StatusInternalServerError, 114 | Code: InternalSQLFailure, 115 | Message: errors.Cause(err).Error(), 116 | } 117 | case *model.OtherServerError: 118 | realErr, ok := errors.Cause(err).(*model.OtherServerError) 119 | if !ok { 120 | logger.Logger.Error(fmt.Sprintf("failed to assert. err = %+v", err)) 121 | return nil 122 | } 123 | 124 | return &handledError{ 125 | BaseError: realErr.BaseErr, 126 | Status: http.StatusInternalServerError, 127 | Code: InternalFailure, 128 | Message: systemError, 129 | } 130 | default: 131 | return &handledError{ 132 | Status: http.StatusInternalServerError, 133 | Code: InternalFailure, 134 | Message: systemError, 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/pages/threads/index.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 151 | 152 | 164 | -------------------------------------------------------------------------------- /server/application/mock/thread.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: server/application/thread.go 3 | 4 | // Package mock_application is a generated GoMock package. 5 | package mock_application 6 | 7 | import ( 8 | context "context" 9 | gomock "github.com/golang/mock/gomock" 10 | model "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 11 | reflect "reflect" 12 | ) 13 | 14 | // MockThreadService is a mock of ThreadService interface 15 | type MockThreadService struct { 16 | ctrl *gomock.Controller 17 | recorder *MockThreadServiceMockRecorder 18 | } 19 | 20 | // MockThreadServiceMockRecorder is the mock recorder for MockThreadService 21 | type MockThreadServiceMockRecorder struct { 22 | mock *MockThreadService 23 | } 24 | 25 | // NewMockThreadService creates a new mock instance 26 | func NewMockThreadService(ctrl *gomock.Controller) *MockThreadService { 27 | mock := &MockThreadService{ctrl: ctrl} 28 | mock.recorder = &MockThreadServiceMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use 33 | func (m *MockThreadService) EXPECT() *MockThreadServiceMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // ListThreads mocks base method 38 | func (m *MockThreadService) ListThreads(ctx context.Context, limit int, cursor uint32) (*model.ThreadList, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "ListThreads", ctx, limit, cursor) 41 | ret0, _ := ret[0].(*model.ThreadList) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // ListThreads indicates an expected call of ListThreads 47 | func (mr *MockThreadServiceMockRecorder) ListThreads(ctx, limit, cursor interface{}) *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListThreads", reflect.TypeOf((*MockThreadService)(nil).ListThreads), ctx, limit, cursor) 50 | } 51 | 52 | // GetThread mocks base method 53 | func (m *MockThreadService) GetThread(ctx context.Context, id uint32) (*model.Thread, error) { 54 | m.ctrl.T.Helper() 55 | ret := m.ctrl.Call(m, "GetThread", ctx, id) 56 | ret0, _ := ret[0].(*model.Thread) 57 | ret1, _ := ret[1].(error) 58 | return ret0, ret1 59 | } 60 | 61 | // GetThread indicates an expected call of GetThread 62 | func (mr *MockThreadServiceMockRecorder) GetThread(ctx, id interface{}) *gomock.Call { 63 | mr.mock.ctrl.T.Helper() 64 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetThread", reflect.TypeOf((*MockThreadService)(nil).GetThread), ctx, id) 65 | } 66 | 67 | // CreateThread mocks base method 68 | func (m *MockThreadService) CreateThread(ctx context.Context, thread *model.Thread) (*model.Thread, error) { 69 | m.ctrl.T.Helper() 70 | ret := m.ctrl.Call(m, "CreateThread", ctx, thread) 71 | ret0, _ := ret[0].(*model.Thread) 72 | ret1, _ := ret[1].(error) 73 | return ret0, ret1 74 | } 75 | 76 | // CreateThread indicates an expected call of CreateThread 77 | func (mr *MockThreadServiceMockRecorder) CreateThread(ctx, thread interface{}) *gomock.Call { 78 | mr.mock.ctrl.T.Helper() 79 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateThread", reflect.TypeOf((*MockThreadService)(nil).CreateThread), ctx, thread) 80 | } 81 | 82 | // UpdateThread mocks base method 83 | func (m *MockThreadService) UpdateThread(ctx context.Context, id uint32, thread *model.Thread) (*model.Thread, error) { 84 | m.ctrl.T.Helper() 85 | ret := m.ctrl.Call(m, "UpdateThread", ctx, id, thread) 86 | ret0, _ := ret[0].(*model.Thread) 87 | ret1, _ := ret[1].(error) 88 | return ret0, ret1 89 | } 90 | 91 | // UpdateThread indicates an expected call of UpdateThread 92 | func (mr *MockThreadServiceMockRecorder) UpdateThread(ctx, id, thread interface{}) *gomock.Call { 93 | mr.mock.ctrl.T.Helper() 94 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateThread", reflect.TypeOf((*MockThreadService)(nil).UpdateThread), ctx, id, thread) 95 | } 96 | 97 | // DeleteThread mocks base method 98 | func (m *MockThreadService) DeleteThread(ctx context.Context, id uint32) error { 99 | m.ctrl.T.Helper() 100 | ret := m.ctrl.Call(m, "DeleteThread", ctx, id) 101 | ret0, _ := ret[0].(error) 102 | return ret0 103 | } 104 | 105 | // DeleteThread indicates an expected call of DeleteThread 106 | func (mr *MockThreadServiceMockRecorder) DeleteThread(ctx, id interface{}) *gomock.Call { 107 | mr.mock.ctrl.T.Helper() 108 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteThread", reflect.TypeOf((*MockThreadService)(nil).DeleteThread), ctx, id) 109 | } 110 | -------------------------------------------------------------------------------- /server/domain/repository/mock/user.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: server/domain/repository/user.go 3 | 4 | // Package mock_repository is a generated GoMock package. 5 | package mock_repository 6 | 7 | import ( 8 | context "context" 9 | gomock "github.com/golang/mock/gomock" 10 | model "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 11 | query "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 12 | reflect "reflect" 13 | ) 14 | 15 | // MockUserRepository is a mock of UserRepository interface 16 | type MockUserRepository struct { 17 | ctrl *gomock.Controller 18 | recorder *MockUserRepositoryMockRecorder 19 | } 20 | 21 | // MockUserRepositoryMockRecorder is the mock recorder for MockUserRepository 22 | type MockUserRepositoryMockRecorder struct { 23 | mock *MockUserRepository 24 | } 25 | 26 | // NewMockUserRepository creates a new mock instance 27 | func NewMockUserRepository(ctrl *gomock.Controller) *MockUserRepository { 28 | mock := &MockUserRepository{ctrl: ctrl} 29 | mock.recorder = &MockUserRepositoryMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use 34 | func (m *MockUserRepository) EXPECT() *MockUserRepositoryMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // GetUserByID mocks base method 39 | func (m_2 *MockUserRepository) GetUserByID(ctx context.Context, m query.SQLManager, id uint32) (*model.User, error) { 40 | m_2.ctrl.T.Helper() 41 | ret := m_2.ctrl.Call(m_2, "GetUserByID", ctx, m, id) 42 | ret0, _ := ret[0].(*model.User) 43 | ret1, _ := ret[1].(error) 44 | return ret0, ret1 45 | } 46 | 47 | // GetUserByID indicates an expected call of GetUserByID 48 | func (mr *MockUserRepositoryMockRecorder) GetUserByID(ctx, m, id interface{}) *gomock.Call { 49 | mr.mock.ctrl.T.Helper() 50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByID", reflect.TypeOf((*MockUserRepository)(nil).GetUserByID), ctx, m, id) 51 | } 52 | 53 | // GetUserByName mocks base method 54 | func (m_2 *MockUserRepository) GetUserByName(ctx context.Context, m query.SQLManager, name string) (*model.User, error) { 55 | m_2.ctrl.T.Helper() 56 | ret := m_2.ctrl.Call(m_2, "GetUserByName", ctx, m, name) 57 | ret0, _ := ret[0].(*model.User) 58 | ret1, _ := ret[1].(error) 59 | return ret0, ret1 60 | } 61 | 62 | // GetUserByName indicates an expected call of GetUserByName 63 | func (mr *MockUserRepositoryMockRecorder) GetUserByName(ctx, m, name interface{}) *gomock.Call { 64 | mr.mock.ctrl.T.Helper() 65 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByName", reflect.TypeOf((*MockUserRepository)(nil).GetUserByName), ctx, m, name) 66 | } 67 | 68 | // InsertUser mocks base method 69 | func (m_2 *MockUserRepository) InsertUser(ctx context.Context, m query.SQLManager, user *model.User) (uint32, error) { 70 | m_2.ctrl.T.Helper() 71 | ret := m_2.ctrl.Call(m_2, "InsertUser", ctx, m, user) 72 | ret0, _ := ret[0].(uint32) 73 | ret1, _ := ret[1].(error) 74 | return ret0, ret1 75 | } 76 | 77 | // InsertUser indicates an expected call of InsertUser 78 | func (mr *MockUserRepositoryMockRecorder) InsertUser(ctx, m, user interface{}) *gomock.Call { 79 | mr.mock.ctrl.T.Helper() 80 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertUser", reflect.TypeOf((*MockUserRepository)(nil).InsertUser), ctx, m, user) 81 | } 82 | 83 | // UpdateUser mocks base method 84 | func (m_2 *MockUserRepository) UpdateUser(ctx context.Context, m query.SQLManager, id uint32, user *model.User) error { 85 | m_2.ctrl.T.Helper() 86 | ret := m_2.ctrl.Call(m_2, "UpdateUser", ctx, m, id, user) 87 | ret0, _ := ret[0].(error) 88 | return ret0 89 | } 90 | 91 | // UpdateUser indicates an expected call of UpdateUser 92 | func (mr *MockUserRepositoryMockRecorder) UpdateUser(ctx, m, id, user interface{}) *gomock.Call { 93 | mr.mock.ctrl.T.Helper() 94 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockUserRepository)(nil).UpdateUser), ctx, m, id, user) 95 | } 96 | 97 | // DeleteUser mocks base method 98 | func (m_2 *MockUserRepository) DeleteUser(ctx context.Context, m query.SQLManager, id uint32) error { 99 | m_2.ctrl.T.Helper() 100 | ret := m_2.ctrl.Call(m_2, "DeleteUser", ctx, m, id) 101 | ret0, _ := ret[0].(error) 102 | return ret0 103 | } 104 | 105 | // DeleteUser indicates an expected call of DeleteUser 106 | func (mr *MockUserRepositoryMockRecorder) DeleteUser(ctx, m, id interface{}) *gomock.Call { 107 | mr.mock.ctrl.T.Helper() 108 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteUser", reflect.TypeOf((*MockUserRepository)(nil).DeleteUser), ctx, m, id) 109 | } 110 | -------------------------------------------------------------------------------- /server/application/mock/comment.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: server/application/comment.go 3 | 4 | // Package mock_application is a generated GoMock package. 5 | package mock_application 6 | 7 | import ( 8 | context "context" 9 | gomock "github.com/golang/mock/gomock" 10 | model "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 11 | reflect "reflect" 12 | ) 13 | 14 | // MockCommentService is a mock of CommentService interface 15 | type MockCommentService struct { 16 | ctrl *gomock.Controller 17 | recorder *MockCommentServiceMockRecorder 18 | } 19 | 20 | // MockCommentServiceMockRecorder is the mock recorder for MockCommentService 21 | type MockCommentServiceMockRecorder struct { 22 | mock *MockCommentService 23 | } 24 | 25 | // NewMockCommentService creates a new mock instance 26 | func NewMockCommentService(ctrl *gomock.Controller) *MockCommentService { 27 | mock := &MockCommentService{ctrl: ctrl} 28 | mock.recorder = &MockCommentServiceMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use 33 | func (m *MockCommentService) EXPECT() *MockCommentServiceMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // ListComments mocks base method 38 | func (m *MockCommentService) ListComments(ctx context.Context, threadID uint32, limit int, cursor uint32) (*model.CommentList, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "ListComments", ctx, threadID, limit, cursor) 41 | ret0, _ := ret[0].(*model.CommentList) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // ListComments indicates an expected call of ListComments 47 | func (mr *MockCommentServiceMockRecorder) ListComments(ctx, threadID, limit, cursor interface{}) *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListComments", reflect.TypeOf((*MockCommentService)(nil).ListComments), ctx, threadID, limit, cursor) 50 | } 51 | 52 | // GetComment mocks base method 53 | func (m *MockCommentService) GetComment(ctx context.Context, id uint32) (*model.Comment, error) { 54 | m.ctrl.T.Helper() 55 | ret := m.ctrl.Call(m, "GetComment", ctx, id) 56 | ret0, _ := ret[0].(*model.Comment) 57 | ret1, _ := ret[1].(error) 58 | return ret0, ret1 59 | } 60 | 61 | // GetComment indicates an expected call of GetComment 62 | func (mr *MockCommentServiceMockRecorder) GetComment(ctx, id interface{}) *gomock.Call { 63 | mr.mock.ctrl.T.Helper() 64 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetComment", reflect.TypeOf((*MockCommentService)(nil).GetComment), ctx, id) 65 | } 66 | 67 | // CreateComment mocks base method 68 | func (m *MockCommentService) CreateComment(ctx context.Context, comment *model.Comment) (*model.Comment, error) { 69 | m.ctrl.T.Helper() 70 | ret := m.ctrl.Call(m, "CreateComment", ctx, comment) 71 | ret0, _ := ret[0].(*model.Comment) 72 | ret1, _ := ret[1].(error) 73 | return ret0, ret1 74 | } 75 | 76 | // CreateComment indicates an expected call of CreateComment 77 | func (mr *MockCommentServiceMockRecorder) CreateComment(ctx, comment interface{}) *gomock.Call { 78 | mr.mock.ctrl.T.Helper() 79 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateComment", reflect.TypeOf((*MockCommentService)(nil).CreateComment), ctx, comment) 80 | } 81 | 82 | // UpdateComment mocks base method 83 | func (m *MockCommentService) UpdateComment(ctx context.Context, id uint32, comment *model.Comment) (*model.Comment, error) { 84 | m.ctrl.T.Helper() 85 | ret := m.ctrl.Call(m, "UpdateComment", ctx, id, comment) 86 | ret0, _ := ret[0].(*model.Comment) 87 | ret1, _ := ret[1].(error) 88 | return ret0, ret1 89 | } 90 | 91 | // UpdateComment indicates an expected call of UpdateComment 92 | func (mr *MockCommentServiceMockRecorder) UpdateComment(ctx, id, comment interface{}) *gomock.Call { 93 | mr.mock.ctrl.T.Helper() 94 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateComment", reflect.TypeOf((*MockCommentService)(nil).UpdateComment), ctx, id, comment) 95 | } 96 | 97 | // DeleteComment mocks base method 98 | func (m *MockCommentService) DeleteComment(ctx context.Context, id uint32) error { 99 | m.ctrl.T.Helper() 100 | ret := m.ctrl.Call(m, "DeleteComment", ctx, id) 101 | ret0, _ := ret[0].(error) 102 | return ret0 103 | } 104 | 105 | // DeleteComment indicates an expected call of DeleteComment 106 | func (mr *MockCommentServiceMockRecorder) DeleteComment(ctx, id interface{}) *gomock.Call { 107 | mr.mock.ctrl.T.Helper() 108 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteComment", reflect.TypeOf((*MockCommentService)(nil).DeleteComment), ctx, id) 109 | } 110 | -------------------------------------------------------------------------------- /server/domain/service/authentication_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | "time" 8 | 9 | "github.com/sekky0905/nuxt-vue-go-chat/server/util" 10 | 11 | "github.com/golang/mock/gomock" 12 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 13 | mock_repository "github.com/sekky0905/nuxt-vue-go-chat/server/domain/repository/mock" 14 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db" 15 | "github.com/sekky0905/nuxt-vue-go-chat/server/testutil" 16 | ) 17 | 18 | func Test_authenticationService_Authenticate(t *testing.T) { 19 | ctrl := gomock.NewController(t) 20 | defer ctrl.Finish() 21 | 22 | testutil.SetFakeTime(time.Now()) 23 | 24 | mockDBM := db.NewDBManager() 25 | mockRepo := mock_repository.NewMockUserRepository(ctrl) 26 | 27 | type args struct { 28 | ctx context.Context 29 | userName string 30 | password string 31 | } 32 | 33 | type mockReturns struct { 34 | user *model.User 35 | err error 36 | } 37 | 38 | hashedPass, err := util.HashPassword(model.PasswordForTest) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | 43 | tests := []struct { 44 | name string 45 | args args 46 | wantOk bool 47 | wantUser *model.User 48 | wantErr error 49 | mockReturns 50 | }{ 51 | { 52 | name: "When appropriate name and password are given, returns false and nil", 53 | args: args{ 54 | ctx: context.Background(), 55 | userName: model.UserNameForTest, 56 | password: model.PasswordForTest, 57 | }, 58 | wantOk: true, 59 | wantUser: &model.User{ 60 | ID: model.UserValidIDForTest, 61 | Name: model.UserNameForTest, 62 | SessionID: model.SessionValidIDForTest, 63 | Password: hashedPass, 64 | CreatedAt: testutil.TimeNow(), 65 | UpdatedAt: testutil.TimeNow(), 66 | }, 67 | wantErr: nil, 68 | mockReturns: mockReturns{ 69 | user: &model.User{ 70 | ID: model.UserValidIDForTest, 71 | Name: model.UserNameForTest, 72 | SessionID: model.SessionValidIDForTest, 73 | Password: hashedPass, 74 | CreatedAt: testutil.TimeNow(), 75 | UpdatedAt: testutil.TimeNow(), 76 | }, 77 | err: nil, 78 | }, 79 | }, 80 | { 81 | name: "When inappropriate password is given, returns false and nil", 82 | args: args{ 83 | ctx: context.Background(), 84 | userName: model.UserNameForTest, 85 | password: "invalidPassword", 86 | }, 87 | wantOk: false, 88 | wantUser: nil, 89 | wantErr: nil, 90 | mockReturns: mockReturns{ 91 | user: &model.User{ 92 | ID: model.UserValidIDForTest, 93 | Name: model.UserNameForTest, 94 | SessionID: model.SessionValidIDForTest, 95 | Password: hashedPass, 96 | CreatedAt: testutil.TimeNow(), 97 | UpdatedAt: testutil.TimeNow(), 98 | }, 99 | err: nil, 100 | }, 101 | }, 102 | { 103 | name: "When user doesn't exist, returns false and error", 104 | args: args{ 105 | ctx: context.Background(), 106 | userName: model.UserNameForTest, 107 | password: model.PasswordForTest, 108 | }, 109 | wantOk: false, 110 | wantUser: nil, 111 | wantErr: &model.AuthenticationErr{ 112 | BaseErr: &model.NoSuchDataError{ 113 | BaseErr: nil, 114 | PropertyName: model.NameProperty, 115 | PropertyValue: "test", 116 | DomainModelName: model.DomainModelNameUser, 117 | }, 118 | }, 119 | mockReturns: mockReturns{ 120 | user: nil, 121 | err: &model.NoSuchDataError{ 122 | BaseErr: nil, 123 | PropertyName: model.NameProperty, 124 | PropertyValue: "test", 125 | DomainModelName: model.DomainModelNameUser, 126 | }, 127 | }, 128 | }, 129 | } 130 | for _, tt := range tests { 131 | t.Run(tt.name, func(t *testing.T) { 132 | s := &authenticationService{ 133 | repo: mockRepo, 134 | } 135 | 136 | ur, ok := s.repo.(*mock_repository.MockUserRepository) 137 | if !ok { 138 | t.Fatal("failed to assert MockUserRepository") 139 | } 140 | 141 | ur.EXPECT().GetUserByName(tt.args.ctx, mockDBM, tt.args.userName).Return(tt.mockReturns.user, tt.mockReturns.err) 142 | 143 | gotOk, gotUser, err := s.Authenticate(tt.args.ctx, mockDBM, tt.args.userName, tt.args.password) 144 | if !reflect.DeepEqual(err, tt.wantErr) { 145 | t.Errorf("authenticationService.Authenticate() error = %v, wantErr %v", err, tt.wantErr) 146 | return 147 | } 148 | if gotOk != tt.wantOk { 149 | t.Errorf("authenticationService.Authenticate() = %v, want %v", gotOk, tt.wantOk) 150 | } 151 | 152 | if !reflect.DeepEqual(gotUser, tt.wantUser) { 153 | t.Errorf("authenticationService.Authenticate() = %v, want %v", gotUser, tt.wantUser) 154 | } 155 | }) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /server/application/comment.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 8 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/repository" 9 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/service" 10 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 11 | ) 12 | 13 | // CommentService is interface of CommentService. 14 | type CommentService interface { 15 | ListComments(ctx context.Context, threadID uint32, limit int, cursor uint32) (*model.CommentList, error) 16 | GetComment(ctx context.Context, id uint32) (*model.Comment, error) 17 | CreateComment(ctx context.Context, comment *model.Comment) (*model.Comment, error) 18 | UpdateComment(ctx context.Context, id uint32, comment *model.Comment) (*model.Comment, error) 19 | DeleteComment(ctx context.Context, id uint32) error 20 | } 21 | 22 | // commentService is application service of comment. 23 | type commentService struct { 24 | m query.DBManager 25 | service service.CommentService 26 | repo repository.CommentRepository 27 | txCloser CloseTransaction 28 | } 29 | 30 | // NewCommentService generates and returns CommentService. 31 | func NewCommentService(m query.DBManager, service service.CommentService, repo repository.CommentRepository, txCloser CloseTransaction) CommentService { 32 | return &commentService{ 33 | m: m, 34 | service: service, 35 | repo: repo, 36 | txCloser: txCloser, 37 | } 38 | } 39 | 40 | // ListThreads gets ThreadList. 41 | func (cs *commentService) ListComments(ctx context.Context, threadID uint32, limit int, cursor uint32) (*model.CommentList, error) { 42 | comments, err := cs.repo.ListComments(ctx, cs.m, threadID, limit, cursor) 43 | if err != nil { 44 | return nil, errors.Wrap(err, "failed to list comments") 45 | } 46 | 47 | return comments, nil 48 | } 49 | 50 | // GetComment gets Comment. 51 | func (cs *commentService) GetComment(ctx context.Context, id uint32) (*model.Comment, error) { 52 | comment, err := cs.repo.GetCommentByID(ctx, cs.m, id) 53 | if err != nil { 54 | return nil, errors.Wrap(err, "failed to get comment by id") 55 | } 56 | 57 | return comment, nil 58 | } 59 | 60 | // CreateComment creates Comment. 61 | func (cs *commentService) CreateComment(ctx context.Context, param *model.Comment) (comment *model.Comment, err error) { 62 | tx, err := cs.m.Begin() 63 | if err != nil { 64 | return nil, beginTxErrorMsg(err) 65 | } 66 | 67 | defer func() { 68 | if err := cs.txCloser(tx, err); err != nil { 69 | err = errors.Wrap(err, "failed to close tx") 70 | } 71 | }() 72 | 73 | id, err := cs.repo.InsertComment(ctx, tx, param) 74 | if err != nil { 75 | return nil, errors.Wrap(err, "failed to insert comment") 76 | } 77 | param.ID = id 78 | 79 | return param, nil 80 | } 81 | 82 | // UpdateComment updates Comment. 83 | func (cs *commentService) UpdateComment(ctx context.Context, id uint32, param *model.Comment) (comment *model.Comment, err error) { 84 | copiedComment := *param 85 | 86 | tx, err := cs.m.Begin() 87 | if err != nil { 88 | return nil, beginTxErrorMsg(err) 89 | } 90 | 91 | defer func() { 92 | if err := cs.txCloser(tx, err); err != nil { 93 | err = errors.Wrap(err, "failed to close tx") 94 | } 95 | }() 96 | 97 | yes, err := cs.service.IsAlreadyExistID(ctx, tx, copiedComment.ID) 98 | if !yes { 99 | err = &model.NoSuchDataError{ 100 | PropertyName: model.IDProperty, 101 | PropertyValue: param.ID, 102 | DomainModelName: model.DomainModelNameComment, 103 | } 104 | return nil, errors.Wrap(err, "does not exists ID") 105 | } 106 | 107 | if err != nil { 108 | return nil, errors.Wrap(err, "failed to is already exist ID") 109 | } 110 | 111 | if err := cs.repo.UpdateComment(ctx, tx, param.ID, &copiedComment); err != nil { 112 | return nil, errors.Wrap(err, "failed to update comment") 113 | } 114 | 115 | return &copiedComment, nil 116 | } 117 | 118 | // DeleteComment deletes Comment. 119 | func (cs *commentService) DeleteComment(ctx context.Context, id uint32) (err error) { 120 | tx, err := cs.m.Begin() 121 | if err != nil { 122 | return beginTxErrorMsg(err) 123 | } 124 | 125 | defer func() { 126 | if err := cs.txCloser(tx, err); err != nil { 127 | err = errors.Wrap(err, "failed to close tx") 128 | } 129 | }() 130 | 131 | yes, err := cs.service.IsAlreadyExistID(ctx, tx, id) 132 | if !yes { 133 | err = &model.NoSuchDataError{ 134 | PropertyName: model.IDProperty, 135 | PropertyValue: id, 136 | DomainModelName: model.DomainModelNameComment, 137 | } 138 | return errors.Wrap(err, "does not exists id") 139 | } 140 | if err != nil { 141 | return errors.Wrap(err, "failed to is already exist id") 142 | } 143 | 144 | if err := cs.repo.DeleteComment(ctx, tx, id); err != nil { 145 | return errors.Wrap(err, "failed to delete comment") 146 | } 147 | 148 | return nil 149 | } 150 | -------------------------------------------------------------------------------- /server/domain/repository/mock/comment.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: server/domain/repository/comment.go 3 | 4 | // Package mock_repository is a generated GoMock package. 5 | package mock_repository 6 | 7 | import ( 8 | context "context" 9 | gomock "github.com/golang/mock/gomock" 10 | model "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 11 | query "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 12 | reflect "reflect" 13 | ) 14 | 15 | // MockCommentRepository is a mock of CommentRepository interface 16 | type MockCommentRepository struct { 17 | ctrl *gomock.Controller 18 | recorder *MockCommentRepositoryMockRecorder 19 | } 20 | 21 | // MockCommentRepositoryMockRecorder is the mock recorder for MockCommentRepository 22 | type MockCommentRepositoryMockRecorder struct { 23 | mock *MockCommentRepository 24 | } 25 | 26 | // NewMockCommentRepository creates a new mock instance 27 | func NewMockCommentRepository(ctrl *gomock.Controller) *MockCommentRepository { 28 | mock := &MockCommentRepository{ctrl: ctrl} 29 | mock.recorder = &MockCommentRepositoryMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use 34 | func (m *MockCommentRepository) EXPECT() *MockCommentRepositoryMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // ListComments mocks base method 39 | func (m_2 *MockCommentRepository) ListComments(ctx context.Context, m query.SQLManager, threadID uint32, limit int, cursor uint32) (*model.CommentList, error) { 40 | m_2.ctrl.T.Helper() 41 | ret := m_2.ctrl.Call(m_2, "ListComments", ctx, m, threadID, limit, cursor) 42 | ret0, _ := ret[0].(*model.CommentList) 43 | ret1, _ := ret[1].(error) 44 | return ret0, ret1 45 | } 46 | 47 | // ListComments indicates an expected call of ListComments 48 | func (mr *MockCommentRepositoryMockRecorder) ListComments(ctx, m, threadID, limit, cursor interface{}) *gomock.Call { 49 | mr.mock.ctrl.T.Helper() 50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListComments", reflect.TypeOf((*MockCommentRepository)(nil).ListComments), ctx, m, threadID, limit, cursor) 51 | } 52 | 53 | // GetCommentByID mocks base method 54 | func (m_2 *MockCommentRepository) GetCommentByID(ctx context.Context, m query.SQLManager, id uint32) (*model.Comment, error) { 55 | m_2.ctrl.T.Helper() 56 | ret := m_2.ctrl.Call(m_2, "GetCommentByID", ctx, m, id) 57 | ret0, _ := ret[0].(*model.Comment) 58 | ret1, _ := ret[1].(error) 59 | return ret0, ret1 60 | } 61 | 62 | // GetCommentByID indicates an expected call of GetCommentByID 63 | func (mr *MockCommentRepositoryMockRecorder) GetCommentByID(ctx, m, id interface{}) *gomock.Call { 64 | mr.mock.ctrl.T.Helper() 65 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCommentByID", reflect.TypeOf((*MockCommentRepository)(nil).GetCommentByID), ctx, m, id) 66 | } 67 | 68 | // InsertComment mocks base method 69 | func (m_2 *MockCommentRepository) InsertComment(ctx context.Context, m query.SQLManager, comment *model.Comment) (uint32, error) { 70 | m_2.ctrl.T.Helper() 71 | ret := m_2.ctrl.Call(m_2, "InsertComment", ctx, m, comment) 72 | ret0, _ := ret[0].(uint32) 73 | ret1, _ := ret[1].(error) 74 | return ret0, ret1 75 | } 76 | 77 | // InsertComment indicates an expected call of InsertComment 78 | func (mr *MockCommentRepositoryMockRecorder) InsertComment(ctx, m, comment interface{}) *gomock.Call { 79 | mr.mock.ctrl.T.Helper() 80 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertComment", reflect.TypeOf((*MockCommentRepository)(nil).InsertComment), ctx, m, comment) 81 | } 82 | 83 | // UpdateComment mocks base method 84 | func (m_2 *MockCommentRepository) UpdateComment(ctx context.Context, m query.SQLManager, id uint32, comment *model.Comment) error { 85 | m_2.ctrl.T.Helper() 86 | ret := m_2.ctrl.Call(m_2, "UpdateComment", ctx, m, id, comment) 87 | ret0, _ := ret[0].(error) 88 | return ret0 89 | } 90 | 91 | // UpdateComment indicates an expected call of UpdateComment 92 | func (mr *MockCommentRepositoryMockRecorder) UpdateComment(ctx, m, id, comment interface{}) *gomock.Call { 93 | mr.mock.ctrl.T.Helper() 94 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateComment", reflect.TypeOf((*MockCommentRepository)(nil).UpdateComment), ctx, m, id, comment) 95 | } 96 | 97 | // DeleteComment mocks base method 98 | func (m_2 *MockCommentRepository) DeleteComment(ctx context.Context, m query.SQLManager, id uint32) error { 99 | m_2.ctrl.T.Helper() 100 | ret := m_2.ctrl.Call(m_2, "DeleteComment", ctx, m, id) 101 | ret0, _ := ret[0].(error) 102 | return ret0 103 | } 104 | 105 | // DeleteComment indicates an expected call of DeleteComment 106 | func (mr *MockCommentRepositoryMockRecorder) DeleteComment(ctx, m, id interface{}) *gomock.Call { 107 | mr.mock.ctrl.T.Helper() 108 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteComment", reflect.TypeOf((*MockCommentRepository)(nil).DeleteComment), ctx, m, id) 109 | } 110 | -------------------------------------------------------------------------------- /server/interface/controller/thread.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/pkg/errors" 9 | "github.com/sekky0905/nuxt-vue-go-chat/server/application" 10 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 11 | ) 12 | 13 | // ThreadController is the interface of ThreadController. 14 | type ThreadController interface { 15 | InitThreadAPI(g *gin.RouterGroup) 16 | ListThreads(g *gin.Context) 17 | GetThread(g *gin.Context) 18 | CreateThread(g *gin.Context) 19 | UpdateThread(g *gin.Context) 20 | DeleteThread(g *gin.Context) 21 | } 22 | 23 | // threadController is the controller of thread. 24 | type threadController struct { 25 | tApp application.ThreadService 26 | } 27 | 28 | // NewThreadController generates and returns ThreadController. 29 | func NewThreadController(tApp application.ThreadService) ThreadController { 30 | return &threadController{ 31 | tApp: tApp, 32 | } 33 | } 34 | 35 | // InitThreadAPI initialize Thread API. 36 | func (c *threadController) InitThreadAPI(g *gin.RouterGroup) { 37 | g.GET("", c.ListThreads) 38 | g.GET("/:threadId", c.GetThread) 39 | g.POST("", c.CreateThread) 40 | g.PUT("/:threadId", c.UpdateThread) 41 | g.DELETE("/:threadId", c.DeleteThread) 42 | } 43 | 44 | // ListThreads gets ThreadList. 45 | func (c *threadController) ListThreads(g *gin.Context) { 46 | limit, err := strconv.Atoi(g.Query("limit")) 47 | if err != nil { 48 | limit = defaultLimit 49 | } 50 | 51 | cursorInt, err := strconv.Atoi(g.Query("cursor")) 52 | if err != nil { 53 | cursorInt = defaultCursor 54 | } 55 | 56 | cursor := uint32(cursorInt) 57 | 58 | ctx := g.Request.Context() 59 | thread, err := c.tApp.ListThreads(ctx, limit, cursor) 60 | if err != nil { 61 | ResponseAndLogError(g, errors.Wrap(err, "failed to list threads")) 62 | return 63 | } 64 | 65 | g.JSON(http.StatusOK, thread) 66 | } 67 | 68 | // GetThread gets Thread. 69 | func (c *threadController) GetThread(g *gin.Context) { 70 | idInt, err := strconv.Atoi(g.Param("id")) 71 | if err != nil { 72 | err = &model.InvalidParamError{ 73 | BaseErr: err, 74 | PropertyName: model.IDProperty, 75 | PropertyValue: g.Param("id"), 76 | } 77 | err = handleValidatorErr(err) 78 | ResponseAndLogError(g, errors.Wrap(err, "failed to change id from string to int")) 79 | return 80 | } 81 | 82 | id := uint32(idInt) 83 | 84 | ctx := g.Request.Context() 85 | thread, err := c.tApp.GetThread(ctx, id) 86 | if err != nil { 87 | ResponseAndLogError(g, errors.Wrap(err, "failed to get thread")) 88 | return 89 | } 90 | 91 | g.JSON(http.StatusOK, thread) 92 | } 93 | 94 | // CreateThread creates Thread. 95 | func (c *threadController) CreateThread(g *gin.Context) { 96 | dto := &ThreadDTO{} 97 | if err := g.BindJSON(dto); err != nil { 98 | err = handleValidatorErr(err) 99 | ResponseAndLogError(g, errors.Wrap(err, "failed to bind json")) 100 | return 101 | } 102 | 103 | param := TranslateFromThreadDTOToThread(dto) 104 | 105 | ctx := g.Request.Context() 106 | thread, err := c.tApp.CreateThread(ctx, param) 107 | if err != nil { 108 | ResponseAndLogError(g, errors.Wrap(err, "failed created thread")) 109 | return 110 | } 111 | 112 | g.JSON(http.StatusOK, thread) 113 | } 114 | 115 | // UpdateThread updates Thread. 116 | func (c *threadController) UpdateThread(g *gin.Context) { 117 | dto := &ThreadDTO{} 118 | if err := g.BindJSON(dto); err != nil { 119 | err = handleValidatorErr(err) 120 | ResponseAndLogError(g, errors.Wrap(err, "failed to bind json")) 121 | return 122 | } 123 | 124 | idInt, err := strconv.Atoi(g.Param("id")) 125 | if err != nil { 126 | err = &model.InvalidParamError{ 127 | BaseErr: err, 128 | PropertyName: model.IDProperty, 129 | PropertyValue: g.Param("id"), 130 | } 131 | err = handleValidatorErr(err) 132 | ResponseAndLogError(g, errors.Wrap(err, "failed to change id from string to int")) 133 | return 134 | } 135 | 136 | id := uint32(idInt) 137 | 138 | param := TranslateFromThreadDTOToThread(dto) 139 | 140 | ctx := g.Request.Context() 141 | thread, err := c.tApp.UpdateThread(ctx, id, param) 142 | if err != nil { 143 | ResponseAndLogError(g, errors.Wrap(err, "failed to update thread")) 144 | return 145 | } 146 | 147 | g.JSON(http.StatusOK, thread) 148 | } 149 | 150 | // DeleteThread deletes Thread. 151 | func (c *threadController) DeleteThread(g *gin.Context) { 152 | idInt, err := strconv.Atoi(g.Param("id")) 153 | if err != nil { 154 | err = &model.InvalidParamError{ 155 | BaseErr: err, 156 | PropertyName: model.IDProperty, 157 | PropertyValue: g.Param("id"), 158 | } 159 | err = handleValidatorErr(err) 160 | ResponseAndLogError(g, errors.Wrap(err, "failed to change id from string to int")) 161 | return 162 | } 163 | 164 | id := uint32(idInt) 165 | 166 | ctx := g.Request.Context() 167 | if err := c.tApp.DeleteThread(ctx, id); err != nil { 168 | ResponseAndLogError(g, errors.Wrap(err, "failed to delete thread")) 169 | return 170 | } 171 | 172 | g.JSON(http.StatusOK, nil) 173 | } 174 | -------------------------------------------------------------------------------- /server/application/thread.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 8 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/repository" 9 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/service" 10 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 11 | ) 12 | 13 | // ThreadService is interface of ThreadService. 14 | type ThreadService interface { 15 | ListThreads(ctx context.Context, limit int, cursor uint32) (*model.ThreadList, error) 16 | GetThread(ctx context.Context, id uint32) (*model.Thread, error) 17 | CreateThread(ctx context.Context, thread *model.Thread) (*model.Thread, error) 18 | UpdateThread(ctx context.Context, id uint32, thread *model.Thread) (*model.Thread, error) 19 | DeleteThread(ctx context.Context, id uint32) error 20 | } 21 | 22 | // threadService is application service of thread. 23 | type threadService struct { 24 | m query.DBManager 25 | service service.ThreadService 26 | repo repository.ThreadRepository 27 | txCloser CloseTransaction 28 | } 29 | 30 | // NewThreadService generates and returns ThreadService. 31 | func NewThreadService(m query.DBManager, service service.ThreadService, repo repository.ThreadRepository, txCloser CloseTransaction) ThreadService { 32 | return &threadService{ 33 | m: m, 34 | service: service, 35 | repo: repo, 36 | txCloser: txCloser, 37 | } 38 | } 39 | 40 | // ListThreads gets ThreadList. 41 | func (a *threadService) ListThreads(ctx context.Context, limit int, cursor uint32) (*model.ThreadList, error) { 42 | threads, err := a.repo.ListThreads(ctx, a.m, cursor, limit) 43 | if err != nil { 44 | return nil, errors.Wrap(err, "failed to thread threads") 45 | } 46 | 47 | return threads, nil 48 | } 49 | 50 | // GetThread gets Thread. 51 | func (a *threadService) GetThread(ctx context.Context, id uint32) (*model.Thread, error) { 52 | thread, err := a.repo.GetThreadByID(ctx, a.m, id) 53 | if err != nil { 54 | return nil, errors.Wrap(err, "failed to get thread by id") 55 | } 56 | 57 | return thread, nil 58 | } 59 | 60 | // CreateThread creates Thread. 61 | func (a *threadService) CreateThread(ctx context.Context, param *model.Thread) (thread *model.Thread, err error) { 62 | tx, err := a.m.Begin() 63 | if err != nil { 64 | return nil, beginTxErrorMsg(err) 65 | } 66 | 67 | defer func() { 68 | if err := a.txCloser(tx, err); err != nil { 69 | err = errors.Wrap(err, "failed to close tx") 70 | } 71 | }() 72 | 73 | yes, err := a.service.IsAlreadyExistTitle(ctx, tx, param.Title) 74 | if yes { 75 | err = &model.AlreadyExistError{ 76 | PropertyName: model.TitleProperty, 77 | PropertyValue: param.Title, 78 | DomainModelName: model.DomainModelNameThread, 79 | } 80 | return nil, errors.Wrap(err, "already exist id") 81 | } 82 | 83 | if _, ok := errors.Cause(err).(*model.NoSuchDataError); !ok { 84 | return nil, errors.Wrap(err, "failed is already exist id") 85 | } 86 | 87 | id, err := a.repo.InsertThread(ctx, tx, param) 88 | if err != nil { 89 | return nil, errors.Wrap(err, "failed to insert thread") 90 | } 91 | param.ID = id 92 | return param, nil 93 | } 94 | 95 | // UpdateThread updates Thread. 96 | func (a *threadService) UpdateThread(ctx context.Context, id uint32, param *model.Thread) (thread *model.Thread, err error) { 97 | copiedThread := *param 98 | tx, err := a.m.Begin() 99 | if err != nil { 100 | return nil, beginTxErrorMsg(err) 101 | } 102 | 103 | defer func() { 104 | if err := a.txCloser(tx, err); err != nil { 105 | err = errors.Wrap(err, "failed to close tx") 106 | } 107 | }() 108 | 109 | yes, err := a.service.IsAlreadyExistID(ctx, tx, copiedThread.ID) 110 | if !yes { 111 | err = &model.NoSuchDataError{ 112 | PropertyName: model.IDProperty, 113 | PropertyValue: param.ID, 114 | DomainModelName: model.DomainModelNameThread, 115 | } 116 | return nil, errors.Wrap(err, "does not exists ID") 117 | } 118 | 119 | if err != nil { 120 | return nil, errors.Wrap(err, "failed to is already exist ID") 121 | } 122 | 123 | if err := a.repo.UpdateThread(ctx, tx, copiedThread.ID, &copiedThread); err != nil { 124 | return nil, errors.Wrap(err, "failed to update thread") 125 | } 126 | 127 | return &copiedThread, nil 128 | } 129 | 130 | // DeleteThread deletes Thread. 131 | func (a *threadService) DeleteThread(ctx context.Context, id uint32) (err error) { 132 | tx, err := a.m.Begin() 133 | if err != nil { 134 | return beginTxErrorMsg(err) 135 | } 136 | 137 | defer func() { 138 | if err := a.txCloser(tx, err); err != nil { 139 | err = errors.Wrap(err, "failed to close tx") 140 | } 141 | }() 142 | 143 | yes, err := a.service.IsAlreadyExistID(ctx, tx, id) 144 | if !yes { 145 | err = &model.NoSuchDataError{ 146 | PropertyName: model.IDProperty, 147 | PropertyValue: id, 148 | DomainModelName: model.DomainModelNameThread, 149 | } 150 | return errors.Wrap(err, "does not exists id") 151 | } 152 | if err != nil { 153 | return errors.Wrap(err, "failed to is already exist id") 154 | } 155 | 156 | if err := a.repo.DeleteThread(ctx, tx, id); err != nil { 157 | return errors.Wrap(err, "failed to delete thread") 158 | } 159 | 160 | return nil 161 | } 162 | -------------------------------------------------------------------------------- /server/infra/db/session.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 8 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/repository" 9 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 10 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/logger" 11 | "go.uber.org/zap" 12 | ) 13 | 14 | // sessionRepository is repository of user. 15 | type sessionRepository struct { 16 | } 17 | 18 | // NewSessionRepository generates and returns sessionRepository. 19 | func NewSessionRepository() repository.SessionRepository { 20 | return &sessionRepository{} 21 | } 22 | 23 | // ErrorMsg generates and returns error message. 24 | func (repo *sessionRepository) ErrorMsg(method model.RepositoryMethod, err error) error { 25 | ex := &model.RepositoryError{ 26 | BaseErr: err, 27 | RepositoryMethod: method, 28 | DomainModelName: model.DomainModelNameSession, 29 | } 30 | 31 | return ex 32 | } 33 | 34 | // GetSessionByID gets and returns a record specified by id. 35 | func (repo *sessionRepository) GetSessionByID(ctx context.Context, m query.SQLManager, id string) (*model.Session, error) { 36 | q := "SELECT id, user_id, created_at FROM sessions WHERE id=?" 37 | 38 | list, err := repo.list(ctx, m, model.RepositoryMethodREAD, q, id) 39 | 40 | if len(list) == 0 { 41 | err = &model.NoSuchDataError{ 42 | BaseErr: err, 43 | PropertyName: model.IDProperty, 44 | PropertyValue: id, 45 | DomainModelName: model.DomainModelNameSession, 46 | } 47 | return nil, errors.Wrapf(err, "session data is 0") 48 | } 49 | 50 | if err != nil { 51 | err = errors.Wrap(err, "failed to list session") 52 | return nil, repo.ErrorMsg(model.RepositoryMethodREAD, err) 53 | } 54 | 55 | return list[0], nil 56 | } 57 | 58 | // list gets and returns list of records. 59 | func (repo *sessionRepository) list(ctx context.Context, m query.SQLManager, method model.RepositoryMethod, q string, args ...interface{}) (sessions []*model.Session, err error) { 60 | stmt, err := m.PrepareContext(ctx, q) 61 | if err != nil { 62 | err = errors.Wrap(err, "failed to prepare context") 63 | return nil, repo.ErrorMsg(method, err) 64 | } 65 | defer func() { 66 | err = stmt.Close() 67 | if err != nil { 68 | logger.Logger.Error("stmt.Close", zap.String("error message", err.Error())) 69 | } 70 | }() 71 | 72 | rows, err := stmt.QueryContext(ctx, args...) 73 | if err != nil { 74 | err = errors.Wrap(err, "failed to execute context") 75 | return nil, repo.ErrorMsg(method, err) 76 | } 77 | defer func() { 78 | err = rows.Close() 79 | if err != nil { 80 | logger.Logger.Error("rows.Close", zap.String("error message", err.Error())) 81 | } 82 | }() 83 | 84 | list := make([]*model.Session, 0) 85 | for rows.Next() { 86 | session := &model.Session{} 87 | 88 | err = rows.Scan( 89 | &session.ID, 90 | &session.UserID, 91 | &session.CreatedAt, 92 | ) 93 | 94 | if err != nil { 95 | err = errors.Wrap(err, "failed to scan rows") 96 | return nil, repo.ErrorMsg(method, err) 97 | } 98 | 99 | list = append(list, session) 100 | } 101 | 102 | return list, nil 103 | } 104 | 105 | // InsertSession insert a record. 106 | func (repo *sessionRepository) InsertSession(ctx context.Context, m query.SQLManager, session *model.Session) error { 107 | q := "INSERT INTO sessions (id, user_id, created_at) VALUES (?, ?, NOW())" 108 | stmt, err := m.PrepareContext(ctx, q) 109 | if err != nil { 110 | err = errors.Wrap(err, "failed to prepare context") 111 | return repo.ErrorMsg(model.RepositoryMethodInsert, err) 112 | } 113 | defer func() { 114 | err = stmt.Close() 115 | if err != nil { 116 | logger.Logger.Error("stmt.Close", zap.String("error message", err.Error())) 117 | } 118 | }() 119 | 120 | result, err := stmt.ExecContext(ctx, session.ID, session.UserID) 121 | if err != nil { 122 | err = errors.Wrap(err, "failed to execute context") 123 | return repo.ErrorMsg(model.RepositoryMethodInsert, err) 124 | } 125 | 126 | affect, err := result.RowsAffected() 127 | if affect != 1 { 128 | err = errors.Errorf("total affected: %d ", affect) 129 | return repo.ErrorMsg(model.RepositoryMethodInsert, err) 130 | } 131 | 132 | return nil 133 | } 134 | 135 | // DeleteSession delete a record. 136 | func (repo *sessionRepository) DeleteSession(ctx context.Context, m query.SQLManager, id string) error { 137 | q := "DELETE FROM sessions WHERE id=?" 138 | 139 | stmt, err := m.PrepareContext(ctx, q) 140 | if err != nil { 141 | err = errors.Wrap(err, "failed to prepare context") 142 | return repo.ErrorMsg(model.RepositoryMethodDELETE, err) 143 | } 144 | defer func() { 145 | err = stmt.Close() 146 | if err != nil { 147 | logger.Logger.Error("stmt.Close", zap.String("error message", err.Error())) 148 | } 149 | }() 150 | 151 | result, err := stmt.ExecContext(ctx, id) 152 | if err != nil { 153 | err = errors.Wrap(err, "failed to execute context") 154 | return repo.ErrorMsg(model.RepositoryMethodDELETE, err) 155 | } 156 | 157 | affect, err := result.RowsAffected() 158 | if err != nil { 159 | err = errors.Wrap(err, "failed to get rows affected") 160 | return repo.ErrorMsg(model.RepositoryMethodDELETE, err) 161 | } 162 | if affect != 1 { 163 | err = errors.Errorf("total affected: %d ", affect) 164 | return repo.ErrorMsg(model.RepositoryMethodDELETE, err) 165 | } 166 | 167 | return nil 168 | } 169 | -------------------------------------------------------------------------------- /server/domain/repository/mock/thread.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: server/domain/repository/thread.go 3 | 4 | // Package mock_repository is a generated GoMock package. 5 | package mock_repository 6 | 7 | import ( 8 | context "context" 9 | gomock "github.com/golang/mock/gomock" 10 | model "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 11 | query "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 12 | reflect "reflect" 13 | ) 14 | 15 | // MockThreadRepository is a mock of ThreadRepository interface 16 | type MockThreadRepository struct { 17 | ctrl *gomock.Controller 18 | recorder *MockThreadRepositoryMockRecorder 19 | } 20 | 21 | // MockThreadRepositoryMockRecorder is the mock recorder for MockThreadRepository 22 | type MockThreadRepositoryMockRecorder struct { 23 | mock *MockThreadRepository 24 | } 25 | 26 | // NewMockThreadRepository creates a new mock instance 27 | func NewMockThreadRepository(ctrl *gomock.Controller) *MockThreadRepository { 28 | mock := &MockThreadRepository{ctrl: ctrl} 29 | mock.recorder = &MockThreadRepositoryMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use 34 | func (m *MockThreadRepository) EXPECT() *MockThreadRepositoryMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // ListThreads mocks base method 39 | func (m_2 *MockThreadRepository) ListThreads(ctx context.Context, m query.SQLManager, cursor uint32, limit int) (*model.ThreadList, error) { 40 | m_2.ctrl.T.Helper() 41 | ret := m_2.ctrl.Call(m_2, "ListThreads", ctx, m, cursor, limit) 42 | ret0, _ := ret[0].(*model.ThreadList) 43 | ret1, _ := ret[1].(error) 44 | return ret0, ret1 45 | } 46 | 47 | // ListThreads indicates an expected call of ListThreads 48 | func (mr *MockThreadRepositoryMockRecorder) ListThreads(ctx, m, cursor, limit interface{}) *gomock.Call { 49 | mr.mock.ctrl.T.Helper() 50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListThreads", reflect.TypeOf((*MockThreadRepository)(nil).ListThreads), ctx, m, cursor, limit) 51 | } 52 | 53 | // GetThreadByID mocks base method 54 | func (m_2 *MockThreadRepository) GetThreadByID(ctx context.Context, m query.SQLManager, id uint32) (*model.Thread, error) { 55 | m_2.ctrl.T.Helper() 56 | ret := m_2.ctrl.Call(m_2, "GetThreadByID", ctx, m, id) 57 | ret0, _ := ret[0].(*model.Thread) 58 | ret1, _ := ret[1].(error) 59 | return ret0, ret1 60 | } 61 | 62 | // GetThreadByID indicates an expected call of GetThreadByID 63 | func (mr *MockThreadRepositoryMockRecorder) GetThreadByID(ctx, m, id interface{}) *gomock.Call { 64 | mr.mock.ctrl.T.Helper() 65 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetThreadByID", reflect.TypeOf((*MockThreadRepository)(nil).GetThreadByID), ctx, m, id) 66 | } 67 | 68 | // GetThreadByTitle mocks base method 69 | func (m_2 *MockThreadRepository) GetThreadByTitle(ctx context.Context, m query.SQLManager, name string) (*model.Thread, error) { 70 | m_2.ctrl.T.Helper() 71 | ret := m_2.ctrl.Call(m_2, "GetThreadByTitle", ctx, m, name) 72 | ret0, _ := ret[0].(*model.Thread) 73 | ret1, _ := ret[1].(error) 74 | return ret0, ret1 75 | } 76 | 77 | // GetThreadByTitle indicates an expected call of GetThreadByTitle 78 | func (mr *MockThreadRepositoryMockRecorder) GetThreadByTitle(ctx, m, name interface{}) *gomock.Call { 79 | mr.mock.ctrl.T.Helper() 80 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetThreadByTitle", reflect.TypeOf((*MockThreadRepository)(nil).GetThreadByTitle), ctx, m, name) 81 | } 82 | 83 | // InsertThread mocks base method 84 | func (m_2 *MockThreadRepository) InsertThread(ctx context.Context, m query.SQLManager, thead *model.Thread) (uint32, error) { 85 | m_2.ctrl.T.Helper() 86 | ret := m_2.ctrl.Call(m_2, "InsertThread", ctx, m, thead) 87 | ret0, _ := ret[0].(uint32) 88 | ret1, _ := ret[1].(error) 89 | return ret0, ret1 90 | } 91 | 92 | // InsertThread indicates an expected call of InsertThread 93 | func (mr *MockThreadRepositoryMockRecorder) InsertThread(ctx, m, thead interface{}) *gomock.Call { 94 | mr.mock.ctrl.T.Helper() 95 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertThread", reflect.TypeOf((*MockThreadRepository)(nil).InsertThread), ctx, m, thead) 96 | } 97 | 98 | // UpdateThread mocks base method 99 | func (m_2 *MockThreadRepository) UpdateThread(ctx context.Context, m query.SQLManager, id uint32, thead *model.Thread) error { 100 | m_2.ctrl.T.Helper() 101 | ret := m_2.ctrl.Call(m_2, "UpdateThread", ctx, m, id, thead) 102 | ret0, _ := ret[0].(error) 103 | return ret0 104 | } 105 | 106 | // UpdateThread indicates an expected call of UpdateThread 107 | func (mr *MockThreadRepositoryMockRecorder) UpdateThread(ctx, m, id, thead interface{}) *gomock.Call { 108 | mr.mock.ctrl.T.Helper() 109 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateThread", reflect.TypeOf((*MockThreadRepository)(nil).UpdateThread), ctx, m, id, thead) 110 | } 111 | 112 | // DeleteThread mocks base method 113 | func (m_2 *MockThreadRepository) DeleteThread(ctx context.Context, m query.SQLManager, id uint32) error { 114 | m_2.ctrl.T.Helper() 115 | ret := m_2.ctrl.Call(m_2, "DeleteThread", ctx, m, id) 116 | ret0, _ := ret[0].(error) 117 | return ret0 118 | } 119 | 120 | // DeleteThread indicates an expected call of DeleteThread 121 | func (mr *MockThreadRepositoryMockRecorder) DeleteThread(ctx, m, id interface{}) *gomock.Call { 122 | mr.mock.ctrl.T.Helper() 123 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteThread", reflect.TypeOf((*MockThreadRepository)(nil).DeleteThread), ctx, m, id) 124 | } 125 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/components/SignUp.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /server/interface/controller/comment.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/pkg/errors" 9 | "github.com/sekky0905/nuxt-vue-go-chat/server/application" 10 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 11 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/logger" 12 | "go.uber.org/zap" 13 | ) 14 | 15 | // CommentController is the interface of CommentController. 16 | type CommentController interface { 17 | InitCommentAPI(g *gin.RouterGroup) 18 | ListComments(g *gin.Context) 19 | GetComment(g *gin.Context) 20 | CreateComment(g *gin.Context) 21 | UpdateComment(g *gin.Context) 22 | DeleteComment(g *gin.Context) 23 | } 24 | 25 | // commentController is the controller of comment. 26 | type commentController struct { 27 | cApp application.CommentService 28 | } 29 | 30 | // InitCommentAPI initialize Comment API. 31 | func (c *commentController) InitCommentAPI(g *gin.RouterGroup) { 32 | g.GET("/:threadId/comments", c.ListComments) 33 | g.GET("/:threadId/comments/:id", c.GetComment) 34 | g.POST("/:threadId/comments", c.CreateComment) 35 | g.PUT("/:threadId/comments/:id", c.UpdateComment) 36 | g.DELETE("/:threadId/comments/:id", c.DeleteComment) 37 | } 38 | 39 | // NewCommentController generates and returns CommentController. 40 | func NewCommentController(cAPP application.CommentService) CommentController { 41 | return &commentController{ 42 | cApp: cAPP, 43 | } 44 | } 45 | 46 | // ListComment gets CommentList. 47 | func (c commentController) ListComments(g *gin.Context) { 48 | limit, err := strconv.Atoi(g.Query("limit")) 49 | if err != nil { 50 | limit = defaultLimit 51 | } 52 | 53 | cursorInt, err := strconv.Atoi(g.Query("cursor")) 54 | if err != nil { 55 | cursorInt = defaultCursor 56 | } 57 | 58 | cursor := uint32(cursorInt) 59 | 60 | threadIInt, err := strconv.Atoi(g.Param("threadId")) 61 | if err != nil || threadIInt < 1 { 62 | err = &model.InvalidParamError{ 63 | BaseErr: err, 64 | PropertyName: model.ThreadIDProperty, 65 | InvalidReason: "threadId should be number and over 0", 66 | } 67 | 68 | ResponseAndLogError(g, errors.Wrap(err, "failed to list comments")) 69 | return 70 | } 71 | 72 | threadID := uint32(threadIInt) 73 | 74 | ctx := g.Request.Context() 75 | comment, err := c.cApp.ListComments(ctx, threadID, limit, cursor) 76 | if err != nil { 77 | ResponseAndLogError(g, errors.Wrap(err, "failed to list comments")) 78 | return 79 | } 80 | 81 | g.JSON(http.StatusOK, comment) 82 | 83 | } 84 | 85 | // GetComment gets Comment. 86 | func (c commentController) GetComment(g *gin.Context) { 87 | idInt, err := strconv.Atoi(g.Param("id")) 88 | if err != nil || idInt < 1 { 89 | logger.Logger.Info("DEBUG>>>", zap.String("ERR", err.Error())) 90 | err = &model.InvalidParamError{ 91 | BaseErr: err, 92 | PropertyName: model.IDProperty, 93 | InvalidReason: "id should be number and over 0", 94 | } 95 | 96 | ResponseAndLogError(g, err) 97 | return 98 | } 99 | 100 | id := uint32(idInt) 101 | 102 | ctx := g.Request.Context() 103 | comment, err := c.cApp.GetComment(ctx, id) 104 | if err != nil { 105 | ResponseAndLogError(g, errors.Wrap(err, "failed to get comment")) 106 | return 107 | } 108 | g.JSON(http.StatusOK, comment) 109 | } 110 | 111 | // CreateComment creates Comment. 112 | func (c commentController) CreateComment(g *gin.Context) { 113 | dto := &CommentDTO{} 114 | if err := g.BindJSON(dto); err != nil { 115 | err = handleValidatorErr(err) 116 | ResponseAndLogError(g, errors.Wrap(err, "failed to bind json")) 117 | return 118 | } 119 | 120 | param := TranslateFromCommentDTOToComment(dto) 121 | 122 | ctx := g.Request.Context() 123 | thread, err := c.cApp.CreateComment(ctx, param) 124 | if err != nil { 125 | ResponseAndLogError(g, errors.Wrap(err, "failed created comment")) 126 | return 127 | } 128 | 129 | g.JSON(http.StatusOK, thread) 130 | } 131 | 132 | // UpdateComment updates Comment. 133 | func (c commentController) UpdateComment(g *gin.Context) { 134 | dto := &CommentDTO{} 135 | if err := g.BindJSON(dto); err != nil { 136 | err = handleValidatorErr(err) 137 | ResponseAndLogError(g, errors.Wrap(err, "failed to bind json")) 138 | return 139 | } 140 | 141 | idInt, err := strconv.Atoi(g.Param("id")) 142 | if err != nil { 143 | err = &model.InvalidParamError{ 144 | BaseErr: err, 145 | PropertyName: model.IDProperty, 146 | PropertyValue: g.Param("id"), 147 | } 148 | err = handleValidatorErr(err) 149 | ResponseAndLogError(g, errors.Wrap(err, "failed to change id from string to int")) 150 | return 151 | } 152 | 153 | id := uint32(idInt) 154 | 155 | param := TranslateFromCommentDTOToComment(dto) 156 | 157 | ctx := g.Request.Context() 158 | thread, err := c.cApp.UpdateComment(ctx, id, param) 159 | if err != nil { 160 | ResponseAndLogError(g, errors.Wrap(err, "failed to update comment")) 161 | return 162 | } 163 | 164 | g.JSON(http.StatusOK, thread) 165 | 166 | } 167 | 168 | // DeleteComment deletes Comment. 169 | func (c commentController) DeleteComment(g *gin.Context) { 170 | idInt, err := strconv.Atoi(g.Param("id")) 171 | if err != nil { 172 | err = &model.InvalidParamError{ 173 | BaseErr: err, 174 | PropertyName: model.IDProperty, 175 | PropertyValue: g.Param("id"), 176 | } 177 | err = handleValidatorErr(err) 178 | ResponseAndLogError(g, errors.Wrap(err, "failed to change id from string to int")) 179 | return 180 | } 181 | 182 | id := uint32(idInt) 183 | 184 | ctx := g.Request.Context() 185 | if err := c.cApp.DeleteComment(ctx, id); err != nil { 186 | ResponseAndLogError(g, errors.Wrap(err, "failed to delete comment")) 187 | return 188 | } 189 | 190 | g.JSON(http.StatusOK, nil) 191 | } 192 | -------------------------------------------------------------------------------- /client/nuxt-vue-go-chat/app/pages/threads/_id/comments/index.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 135 | 136 | 229 | -------------------------------------------------------------------------------- /server/domain/service/user_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 8 | 9 | "github.com/golang/mock/gomock" 10 | "github.com/pkg/errors" 11 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 12 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/repository" 13 | mock_repository "github.com/sekky0905/nuxt-vue-go-chat/server/domain/repository/mock" 14 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db" 15 | "github.com/sekky0905/nuxt-vue-go-chat/server/testutil" 16 | ) 17 | 18 | func Test_userService_IsAlreadyExistID(t *testing.T) { 19 | // for gomock 20 | ctrl := gomock.NewController(t) 21 | defer ctrl.Finish() 22 | 23 | mock := mock_repository.NewMockUserRepository(ctrl) 24 | 25 | type fields struct { 26 | repo repository.UserRepository 27 | } 28 | type args struct { 29 | ctx context.Context 30 | m query.DBManager 31 | id uint32 32 | } 33 | 34 | type returnArgs struct { 35 | user *model.User 36 | err error 37 | } 38 | 39 | tests := []struct { 40 | name string 41 | fields fields 42 | args args 43 | returnArgs 44 | want bool 45 | wantErr error 46 | }{ 47 | { 48 | name: "When specified user already exists, return true and nil.", 49 | fields: fields{ 50 | repo: mock, 51 | }, 52 | args: args{ 53 | ctx: context.Background(), 54 | m: db.NewDBManager(), 55 | id: model.UserValidIDForTest, 56 | }, 57 | returnArgs: returnArgs{ 58 | user: &model.User{ 59 | ID: model.UserValidIDForTest, 60 | Name: model.UserNameForTest, 61 | SessionID: model.SessionValidIDForTest, 62 | Password: model.PasswordForTest, 63 | CreatedAt: testutil.TimeNow(), 64 | UpdatedAt: testutil.TimeNow(), 65 | }, 66 | err: nil, 67 | }, 68 | want: true, 69 | wantErr: nil, 70 | }, 71 | { 72 | name: "When specified user doesn't already exists, return true and nil.", 73 | fields: fields{ 74 | repo: mock, 75 | }, 76 | args: args{ 77 | ctx: context.Background(), 78 | m: db.NewDBManager(), 79 | id: model.UserInValidIDForTest, 80 | }, 81 | returnArgs: returnArgs{ 82 | user: nil, 83 | err: nil, 84 | }, 85 | want: false, 86 | wantErr: nil, 87 | }, 88 | { 89 | name: "When some error has occurred, return false and error.", 90 | fields: fields{ 91 | repo: mock, 92 | }, 93 | args: args{ 94 | ctx: context.Background(), 95 | m: db.NewDBManager(), 96 | id: model.UserInValidIDForTest, 97 | }, 98 | returnArgs: returnArgs{ 99 | user: nil, 100 | err: errors.New(model.ErrorMessageForTest), 101 | }, 102 | want: false, 103 | wantErr: errors.New(model.ErrorMessageForTest), 104 | }, 105 | } 106 | for _, tt := range tests { 107 | t.Run(tt.name, func(t *testing.T) { 108 | s := &userService{ 109 | repo: mock, 110 | } 111 | 112 | mock.EXPECT().GetUserByID(tt.args.ctx, tt.args.m, tt.args.id).Return(tt.returnArgs.user, tt.returnArgs.err) 113 | 114 | got, err := s.IsAlreadyExistID(tt.args.ctx, tt.args.m, tt.args.id) 115 | if tt.wantErr != nil { 116 | if errors.Cause(err).Error() != tt.wantErr.Error() { 117 | t.Errorf("userService.IsAlreadyExistID() error = %v, wantErr %v", err, tt.wantErr) 118 | return 119 | } 120 | } 121 | if got != tt.want { 122 | t.Errorf("userService.IsAlreadyExistID() = %v, want %v", got, tt.want) 123 | } 124 | }) 125 | } 126 | } 127 | 128 | func Test_userService_IsAlreadyExistName(t *testing.T) { 129 | // for gomock 130 | ctrl := gomock.NewController(t) 131 | defer ctrl.Finish() 132 | 133 | mock := mock_repository.NewMockUserRepository(ctrl) 134 | 135 | type fields struct { 136 | repo repository.UserRepository 137 | } 138 | type args struct { 139 | ctx context.Context 140 | m query.DBManager 141 | name string 142 | } 143 | 144 | type returnArgs struct { 145 | user *model.User 146 | err error 147 | } 148 | 149 | tests := []struct { 150 | name string 151 | fields fields 152 | args args 153 | returnArgs 154 | want bool 155 | wantErr error 156 | }{ 157 | { 158 | name: "When specified user already exists, return true and nil.", 159 | fields: fields{ 160 | repo: mock, 161 | }, 162 | args: args{ 163 | ctx: context.Background(), 164 | name: model.UserNameForTest, 165 | }, 166 | returnArgs: returnArgs{ 167 | user: &model.User{ 168 | ID: model.UserValidIDForTest, 169 | Name: model.UserNameForTest, 170 | SessionID: model.SessionValidIDForTest, 171 | Password: model.PasswordForTest, 172 | CreatedAt: testutil.TimeNow(), 173 | UpdatedAt: testutil.TimeNow(), 174 | }, 175 | err: nil, 176 | }, 177 | want: true, 178 | wantErr: nil, 179 | }, 180 | { 181 | name: "When specified user doesn't already exists, return true and nil.", 182 | fields: fields{ 183 | repo: mock, 184 | }, 185 | args: args{ 186 | ctx: context.Background(), 187 | m: db.NewDBManager(), 188 | name: model.UserNameForTest, 189 | }, 190 | returnArgs: returnArgs{ 191 | user: nil, 192 | err: nil, 193 | }, 194 | want: false, 195 | wantErr: nil, 196 | }, 197 | { 198 | name: "When some error has occurred, return false and error.", 199 | fields: fields{ 200 | repo: mock, 201 | }, 202 | args: args{ 203 | ctx: context.Background(), 204 | m: db.NewDBManager(), 205 | name: model.UserNameForTest, 206 | }, 207 | returnArgs: returnArgs{ 208 | user: nil, 209 | err: errors.New(model.ErrorMessageForTest), 210 | }, 211 | want: false, 212 | wantErr: errors.New(model.ErrorMessageForTest), 213 | }, 214 | } 215 | for _, tt := range tests { 216 | t.Run(tt.name, func(t *testing.T) { 217 | s := &userService{ 218 | repo: mock, 219 | } 220 | 221 | mock.EXPECT().GetUserByName(tt.args.ctx, tt.args.m, tt.args.name).Return(tt.returnArgs.user, tt.returnArgs.err) 222 | 223 | got, err := s.IsAlreadyExistName(tt.args.ctx, tt.args.m, tt.args.name) 224 | if tt.wantErr != nil { 225 | if errors.Cause(err).Error() != tt.wantErr.Error() { 226 | t.Errorf("userService.IsAlreadyExistName() error = %v, wantErr %v", err, tt.wantErr) 227 | return 228 | } 229 | } 230 | if got != tt.want { 231 | t.Errorf("userService.IsAlreadyExistName() = %v, want %v", got, tt.want) 232 | } 233 | }) 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /server/domain/service/thread_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/golang/mock/gomock" 8 | "github.com/pkg/errors" 9 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/model" 10 | "github.com/sekky0905/nuxt-vue-go-chat/server/domain/repository" 11 | mock_repository "github.com/sekky0905/nuxt-vue-go-chat/server/domain/repository/mock" 12 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db" 13 | "github.com/sekky0905/nuxt-vue-go-chat/server/infra/db/query" 14 | "github.com/sekky0905/nuxt-vue-go-chat/server/testutil" 15 | ) 16 | 17 | func Test_threadService_IsAlreadyExistID(t *testing.T) { 18 | // for gomock 19 | ctrl := gomock.NewController(t) 20 | defer ctrl.Finish() 21 | 22 | mock := mock_repository.NewMockThreadRepository(ctrl) 23 | 24 | type fields struct { 25 | repo repository.ThreadRepository 26 | } 27 | type args struct { 28 | ctx context.Context 29 | m query.SQLManager 30 | id uint32 31 | } 32 | 33 | type returnArgs struct { 34 | thread *model.Thread 35 | err error 36 | } 37 | 38 | tests := []struct { 39 | name string 40 | fields fields 41 | args args 42 | returnArgs 43 | want bool 44 | wantErr error 45 | }{ 46 | { 47 | name: "When specified thread already exists, return true and nil.", 48 | fields: fields{ 49 | repo: mock, 50 | }, 51 | args: args{ 52 | ctx: context.Background(), 53 | m: db.NewDBManager(), 54 | id: model.ThreadValidIDForTest, 55 | }, 56 | returnArgs: returnArgs{ 57 | thread: &model.Thread{ 58 | ID: model.ThreadValidIDForTest, 59 | Title: model.TitleForTest, 60 | User: &model.User{ 61 | ID: model.UserValidIDForTest, 62 | Name: model.UserNameForTest, 63 | }, 64 | CreatedAt: testutil.TimeNow(), 65 | UpdatedAt: testutil.TimeNow(), 66 | }, 67 | err: nil, 68 | }, 69 | want: true, 70 | wantErr: nil, 71 | }, 72 | { 73 | name: "When specified thread doesn't already exists, return true and nil.", 74 | fields: fields{ 75 | repo: mock, 76 | }, 77 | args: args{ 78 | ctx: context.Background(), 79 | m: db.NewDBManager(), 80 | id: model.ThreadInValidIDForTest, 81 | }, 82 | returnArgs: returnArgs{ 83 | thread: nil, 84 | err: nil, 85 | }, 86 | want: false, 87 | wantErr: nil, 88 | }, 89 | { 90 | name: "When some error has occurred, return false and error.", 91 | fields: fields{ 92 | repo: mock, 93 | }, 94 | args: args{ 95 | ctx: context.Background(), 96 | m: db.NewDBManager(), 97 | id: model.ThreadInValidIDForTest, 98 | }, 99 | returnArgs: returnArgs{ 100 | thread: nil, 101 | err: errors.New(model.ErrorMessageForTest), 102 | }, 103 | want: false, 104 | wantErr: errors.New(model.ErrorMessageForTest), 105 | }, 106 | } 107 | for _, tt := range tests { 108 | t.Run(tt.name, func(t *testing.T) { 109 | s := &threadService{ 110 | repo: mock, 111 | } 112 | 113 | mock.EXPECT().GetThreadByID(tt.args.ctx, tt.args.m, tt.args.id).Return(tt.returnArgs.thread, tt.returnArgs.err) 114 | 115 | got, err := s.IsAlreadyExistID(tt.args.ctx, tt.args.m, tt.args.id) 116 | if tt.wantErr != nil { 117 | if errors.Cause(err).Error() != tt.wantErr.Error() { 118 | t.Errorf("threadService.IsAlreadyExistID() error = %v, wantErr %v", err, tt.wantErr) 119 | return 120 | } 121 | } 122 | if got != tt.want { 123 | t.Errorf("threadService.IsAlreadyExistID() = %v, want %v", got, tt.want) 124 | } 125 | }) 126 | } 127 | } 128 | 129 | func Test_threadService_IsAlreadyExistTitle(t *testing.T) { 130 | // for gomock 131 | ctrl := gomock.NewController(t) 132 | defer ctrl.Finish() 133 | 134 | mock := mock_repository.NewMockThreadRepository(ctrl) 135 | 136 | type fields struct { 137 | repo repository.ThreadRepository 138 | } 139 | type args struct { 140 | ctx context.Context 141 | m query.SQLManager 142 | title string 143 | } 144 | 145 | type returnArgs struct { 146 | thread *model.Thread 147 | err error 148 | } 149 | 150 | tests := []struct { 151 | name string 152 | fields fields 153 | args args 154 | returnArgs 155 | want bool 156 | wantErr error 157 | }{ 158 | { 159 | name: "When specified thread already exists, return true and nil.", 160 | fields: fields{ 161 | repo: mock, 162 | }, 163 | args: args{ 164 | ctx: context.Background(), 165 | m: db.NewDBManager(), 166 | title: model.TitleForTest, 167 | }, 168 | returnArgs: returnArgs{ 169 | thread: &model.Thread{ 170 | ID: model.ThreadValidIDForTest, 171 | Title: model.TitleForTest, 172 | User: &model.User{ 173 | ID: model.UserValidIDForTest, 174 | Name: model.UserNameForTest, 175 | }, 176 | CreatedAt: testutil.TimeNow(), 177 | UpdatedAt: testutil.TimeNow(), 178 | }, 179 | err: nil, 180 | }, 181 | want: true, 182 | wantErr: nil, 183 | }, 184 | { 185 | name: "When specified thread doesn't already exists, return true and nil.", 186 | fields: fields{ 187 | repo: mock, 188 | }, 189 | args: args{ 190 | ctx: context.Background(), 191 | m: db.NewDBManager(), 192 | title: model.TitleForTest, 193 | }, 194 | returnArgs: returnArgs{ 195 | thread: nil, 196 | err: nil, 197 | }, 198 | want: false, 199 | wantErr: nil, 200 | }, 201 | { 202 | name: "When some error has occurred, return false and error.", 203 | fields: fields{ 204 | repo: mock, 205 | }, 206 | args: args{ 207 | ctx: context.Background(), 208 | m: db.NewDBManager(), 209 | title: model.TitleForTest, 210 | }, 211 | returnArgs: returnArgs{ 212 | thread: nil, 213 | err: errors.New(model.ErrorMessageForTest), 214 | }, 215 | want: false, 216 | wantErr: errors.New(model.ErrorMessageForTest), 217 | }, 218 | } 219 | for _, tt := range tests { 220 | t.Run(tt.name, func(t *testing.T) { 221 | s := &threadService{ 222 | repo: mock, 223 | } 224 | 225 | mock.EXPECT().GetThreadByTitle(tt.args.ctx, tt.args.m, tt.args.title).Return(tt.returnArgs.thread, tt.returnArgs.err) 226 | 227 | got, err := s.IsAlreadyExistTitle(tt.args.ctx, tt.args.m, tt.args.title) 228 | if tt.wantErr != nil { 229 | if errors.Cause(err).Error() != tt.wantErr.Error() { 230 | t.Errorf("threadService.IsAlreadyExistTitle() error = %v, wantErr %v", err, tt.wantErr) 231 | return 232 | } 233 | } 234 | if got != tt.want { 235 | t.Errorf("threadService.IsAlreadyExistTitle() = %v, want %v", got, tt.want) 236 | } 237 | }) 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /server/Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | branch = "master" 6 | digest = "1:a4b5a6b32f33323c7850fae4ec085a0fbac3be93c057b6b8cef5bcabe24645ac" 7 | name = "github.com/gin-contrib/sse" 8 | packages = ["."] 9 | pruneopts = "UT" 10 | revision = "5545eab6dad3bbbd6c5ae9186383c2a9d23c0dae" 11 | 12 | [[projects]] 13 | digest = "1:d5083934eb25e45d17f72ffa86cae3814f4a9d6c073c4f16b64147169b245606" 14 | name = "github.com/gin-gonic/gin" 15 | packages = [ 16 | ".", 17 | "binding", 18 | "json", 19 | "render", 20 | ] 21 | pruneopts = "UT" 22 | revision = "b869fe1415e4b9eb52f247441830d502aece2d4d" 23 | version = "v1.3.0" 24 | 25 | [[projects]] 26 | digest = "1:ec6f9bf5e274c833c911923c9193867f3f18788c461f76f05f62bb1510e0ae65" 27 | name = "github.com/go-sql-driver/mysql" 28 | packages = ["."] 29 | pruneopts = "UT" 30 | revision = "72cd26f257d44c1114970e19afddcd812016007e" 31 | version = "v1.4.1" 32 | 33 | [[projects]] 34 | digest = "1:b60efdeb75d3c0ceed88783ac2495256aba3491a537d0f31401202579fd62a94" 35 | name = "github.com/golang/mock" 36 | packages = ["gomock"] 37 | pruneopts = "UT" 38 | revision = "51421b967af1f557f93a59e0057aaf15ca02e29c" 39 | version = "v1.2.0" 40 | 41 | [[projects]] 42 | digest = "1:318f1c959a8a740366fce4b1e1eb2fd914036b4af58fbd0a003349b305f118ad" 43 | name = "github.com/golang/protobuf" 44 | packages = ["proto"] 45 | pruneopts = "UT" 46 | revision = "b5d812f8a3706043e23a9cd5babf2e5423744d30" 47 | version = "v1.3.1" 48 | 49 | [[projects]] 50 | digest = "1:582b704bebaa06b48c29b0cec224a6058a09c86883aaddabde889cd1a5f73e1b" 51 | name = "github.com/google/uuid" 52 | packages = ["."] 53 | pruneopts = "UT" 54 | revision = "0cd6bf5da1e1c83f8b45653022c74f71af0538a4" 55 | version = "v1.1.1" 56 | 57 | [[projects]] 58 | digest = "1:f5a2051c55d05548d2d4fd23d244027b59fbd943217df8aa3b5e170ac2fd6e1b" 59 | name = "github.com/json-iterator/go" 60 | packages = ["."] 61 | pruneopts = "UT" 62 | revision = "0ff49de124c6f76f8494e194af75bde0f1a49a29" 63 | version = "v1.1.6" 64 | 65 | [[projects]] 66 | digest = "1:e150b5fafbd7607e2d638e4e5cf43aa4100124e5593385147b0a74e2733d8b0d" 67 | name = "github.com/mattn/go-isatty" 68 | packages = ["."] 69 | pruneopts = "UT" 70 | revision = "c2a7a6ca930a4cd0bc33a3f298eb71960732a3a7" 71 | version = "v0.0.7" 72 | 73 | [[projects]] 74 | digest = "1:33422d238f147d247752996a26574ac48dcf472976eda7f5134015f06bf16563" 75 | name = "github.com/modern-go/concurrent" 76 | packages = ["."] 77 | pruneopts = "UT" 78 | revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94" 79 | version = "1.0.3" 80 | 81 | [[projects]] 82 | digest = "1:e32bdbdb7c377a07a9a46378290059822efdce5c8d96fe71940d87cb4f918855" 83 | name = "github.com/modern-go/reflect2" 84 | packages = ["."] 85 | pruneopts = "UT" 86 | revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd" 87 | version = "1.0.1" 88 | 89 | [[projects]] 90 | digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b" 91 | name = "github.com/pkg/errors" 92 | packages = ["."] 93 | pruneopts = "UT" 94 | revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4" 95 | version = "v0.8.1" 96 | 97 | [[projects]] 98 | digest = "1:4887e9e89c80299aa520d718239809fdd2a47a9aa394909b169959bfbc424ddf" 99 | name = "github.com/ugorji/go" 100 | packages = ["codec"] 101 | pruneopts = "UT" 102 | revision = "8fd0f8d918c8f0b52d0af210a812ba882cc31a1e" 103 | version = "v1.1.2" 104 | 105 | [[projects]] 106 | digest = "1:3c1a69cdae3501bf75e76d0d86dc6f2b0a7421bc205c0cb7b96b19eed464a34d" 107 | name = "go.uber.org/atomic" 108 | packages = ["."] 109 | pruneopts = "UT" 110 | revision = "1ea20fb1cbb1cc08cbd0d913a96dead89aa18289" 111 | version = "v1.3.2" 112 | 113 | [[projects]] 114 | digest = "1:60bf2a5e347af463c42ed31a493d817f8a72f102543060ed992754e689805d1a" 115 | name = "go.uber.org/multierr" 116 | packages = ["."] 117 | pruneopts = "UT" 118 | revision = "3c4937480c32f4c13a875a1829af76c98ca3d40a" 119 | version = "v1.1.0" 120 | 121 | [[projects]] 122 | digest = "1:c52caf7bd44f92e54627a31b85baf06a68333a196b3d8d241480a774733dcf8b" 123 | name = "go.uber.org/zap" 124 | packages = [ 125 | ".", 126 | "buffer", 127 | "internal/bufferpool", 128 | "internal/color", 129 | "internal/exit", 130 | "zapcore", 131 | ] 132 | pruneopts = "UT" 133 | revision = "ff33455a0e382e8a81d14dd7c922020b6b5e7982" 134 | version = "v1.9.1" 135 | 136 | [[projects]] 137 | branch = "master" 138 | digest = "1:9d5b5d543996dd584da1db1e0de1926f3e4c3a8dba0fa2f8db70f3ebee2342e0" 139 | name = "golang.org/x/crypto" 140 | packages = [ 141 | "bcrypt", 142 | "blowfish", 143 | ] 144 | pruneopts = "UT" 145 | revision = "8dd112bcdc25174059e45e07517d9fc663123347" 146 | 147 | [[projects]] 148 | branch = "master" 149 | digest = "1:1fe78b1a9b9d1ca7ec9a48d680abb426b013c28e95a5f6ebb797b22f657071b7" 150 | name = "golang.org/x/sys" 151 | packages = ["unix"] 152 | pruneopts = "UT" 153 | revision = "3e9a981b8ddba4cb37815d4ebf2171df73c5255b" 154 | 155 | [[projects]] 156 | digest = "1:c25289f43ac4a68d88b02245742347c94f1e108c534dda442188015ff80669b3" 157 | name = "google.golang.org/appengine" 158 | packages = ["cloudsql"] 159 | pruneopts = "UT" 160 | revision = "e9657d882bb81064595ca3b56cbe2546bbabf7b1" 161 | version = "v1.4.0" 162 | 163 | [[projects]] 164 | digest = "1:c84a587136cb69cecc11f3dbe9f9001444044c0dba74997b07f7e4c150b07cda" 165 | name = "gopkg.in/DATA-DOG/go-sqlmock.v1" 166 | packages = ["."] 167 | pruneopts = "UT" 168 | revision = "3f9954f6f6697845b082ca57995849ddf614f450" 169 | version = "v1.3.3" 170 | 171 | [[projects]] 172 | digest = "1:cbc72c4c4886a918d6ab4b95e347ffe259846260f99ebdd8a198c2331cf2b2e9" 173 | name = "gopkg.in/go-playground/validator.v8" 174 | packages = ["."] 175 | pruneopts = "UT" 176 | revision = "5f1438d3fca68893a817e4a66806cea46a9e4ebf" 177 | version = "v8.18.2" 178 | 179 | [[projects]] 180 | digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96" 181 | name = "gopkg.in/yaml.v2" 182 | packages = ["."] 183 | pruneopts = "UT" 184 | revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" 185 | version = "v2.2.2" 186 | 187 | [solve-meta] 188 | analyzer-name = "dep" 189 | analyzer-version = 1 190 | input-imports = [ 191 | "github.com/gin-gonic/gin", 192 | "github.com/go-sql-driver/mysql", 193 | "github.com/golang/mock/gomock", 194 | "github.com/google/uuid", 195 | "github.com/pkg/errors", 196 | "go.uber.org/zap", 197 | "go.uber.org/zap/zapcore", 198 | "golang.org/x/crypto/bcrypt", 199 | "gopkg.in/DATA-DOG/go-sqlmock.v1", 200 | "gopkg.in/go-playground/validator.v8", 201 | ] 202 | solver-name = "gps-cdcl" 203 | solver-version = 1 204 | --------------------------------------------------------------------------------