├── .gitignore ├── .vscode └── launch.json ├── Dockerfile ├── LICENSE ├── api ├── response │ └── response.go ├── route.go └── v1 │ ├── articles.go │ ├── authentication.go │ ├── commands.go │ ├── oauth.go │ ├── roles.go │ ├── upload.go │ └── users.go ├── backend.dockerfile ├── config.yml ├── db ├── gorm.go ├── init_db.sql └── migration.go ├── docker-compose.override.yml ├── docker-compose.yml ├── docs ├── docs.go ├── swagger.json └── swagger.yaml ├── document ├── API.adoc ├── API.html ├── images │ ├── goyangi-howitworks.png │ ├── goyangi-stack.png │ ├── goyangi-vuejs-board-kr.JPG │ ├── goyangi-vuejs-board.JPG │ └── goyangi-vuejs-home.JPG ├── packages │ ├── backend.md │ └── frontend.md └── stylesheets │ ├── asciidoctor.css │ ├── colony.css │ ├── foundation-lime.css │ ├── foundation-potion.css │ ├── foundation.css │ ├── github.css │ ├── golo.css │ ├── iconic.css │ ├── maker.css │ ├── readthedocs.css │ ├── riak.css │ ├── rocket-panda.css │ └── rubygems.css ├── form └── common.go ├── frontend └── vuejs │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── babel.config.js │ ├── nginx-backend-not-found.conf │ ├── nginx.conf │ ├── package.json │ ├── public │ ├── favicon.ico │ └── index.html │ ├── src │ ├── api │ │ ├── ArticleAPI.js │ │ ├── AuthenticationAPI.js │ │ ├── BaseAPI.js │ │ ├── UserAPI.js │ │ └── index.js │ ├── assets │ │ ├── goyangi.jpg │ │ └── logo.png │ ├── components │ │ ├── articles │ │ │ ├── Form.vue │ │ │ ├── Item.vue │ │ │ └── List.vue │ │ ├── comments │ │ │ ├── Form.vue │ │ │ ├── List.vue │ │ │ └── index.vue │ │ └── likings │ │ │ └── index.vue │ ├── layouts │ │ └── BasicLayout.vue │ ├── locales │ │ ├── en │ │ │ └── translation.json │ │ └── ko │ │ │ └── translation.json │ ├── main.js │ ├── router │ │ ├── articles │ │ │ └── index.js │ │ ├── index.js │ │ └── users │ │ │ └── index.js │ ├── settings │ │ ├── api_base.js │ │ └── i18next.js │ ├── store │ │ ├── actions.js │ │ ├── getters.js │ │ ├── index.js │ │ ├── modules │ │ │ ├── articles │ │ │ │ ├── actions.js │ │ │ │ ├── getters.js │ │ │ │ ├── index.js │ │ │ │ └── mutations.js │ │ │ ├── authentications │ │ │ │ ├── actions.js │ │ │ │ ├── getters.js │ │ │ │ ├── index.js │ │ │ │ └── mutations.js │ │ │ ├── languages │ │ │ │ ├── actions.js │ │ │ │ ├── getters.js │ │ │ │ ├── index.js │ │ │ │ └── mutations.js │ │ │ └── users │ │ │ │ ├── actions.js │ │ │ │ ├── getters.js │ │ │ │ ├── index.js │ │ │ │ └── mutations.js │ │ ├── mutation-types.js │ │ ├── mutations.js │ │ └── state.js │ └── views │ │ ├── App.vue │ │ ├── Home.vue │ │ ├── articles │ │ └── index.vue │ │ └── users │ │ ├── Login.vue │ │ └── Registration.vue │ └── yarn.lock ├── go.mod ├── go.sum ├── log └── .gitignore ├── main.go ├── model ├── app.go ├── article.go ├── common.go ├── file.go ├── language.go ├── role.go └── user.go ├── readme.md ├── readme_ko.md ├── script ├── commands.go ├── constant.go ├── generateAPI.go ├── init.go ├── script.go ├── server.go └── test.go ├── server ├── exec │ └── main.go └── server.go ├── service ├── articleService │ ├── article.go │ ├── comment.go │ ├── form.go │ ├── liking.go │ └── share.go ├── commentService │ ├── comment.go │ └── form.go ├── doc.go ├── likingService │ ├── form.go │ └── liking.go ├── oauthService │ ├── common.go │ ├── facebook.go │ ├── github.go │ ├── google.go │ ├── kakao.go │ ├── linkedin.go │ ├── model.go │ ├── naver.go │ ├── twitter.go │ └── yahoo.go ├── roleService │ ├── form.go │ └── role.go ├── uploadService │ ├── file.go │ ├── form.go │ └── image.go └── userService │ ├── authentication.go │ ├── cookie.go │ ├── form.go │ ├── locale │ ├── en-us.all.json │ └── ko-kr.all.json │ ├── password.go │ ├── user.go │ ├── userLiking │ └── liking.go │ ├── userPermission │ └── permission.go │ └── verification.go └── util ├── authHelper ├── app.go ├── authHelper_suite_test.go ├── authHelper_test.go ├── header.go └── user.go ├── aws ├── aws.go ├── elasticache.go └── s3.go ├── concurrency ├── concurrency.go ├── concurrency_suite_test.go └── concurrency_test.go ├── crypto ├── crypto_suite_test.go ├── md5.go ├── md5_test.go ├── token.go └── token_test.go ├── email └── email.go ├── file ├── file.go ├── file_suite_test.go └── file_test.go ├── ginHelper └── request.go ├── httpHelper └── http.go ├── image ├── image.go ├── image_suite_test.go ├── image_test.go └── testdata │ ├── cat.gif │ ├── cat.jpg │ └── cat.png ├── interfaceHelper ├── interfaceHelper.go ├── interfaceHelper_suite_test.go └── interfaceHelper_test.go ├── jwt ├── jwt.go ├── jwt_suite_test.go └── jwt_test.go ├── log ├── error.go ├── ginLogger.go └── logger.go ├── modelHelper ├── modelHelper.go ├── modelHelper_suite_test.go └── modelHelper_test.go ├── oauth2 ├── facebook │ └── facebook.go ├── github │ └── github.go ├── google │ └── google.go ├── helper.go ├── kakao │ └── kakao.go ├── linkedin │ └── linkedin.go ├── naver │ └── naver.go ├── oauth2.go ├── twitter │ └── twitter.go └── yahoo │ └── yahoo.go ├── pagination ├── pagination.go ├── pagination_suite_test.go └── pagination_test.go ├── random ├── random.go ├── random_suite_test.go └── random_test.go ├── redis ├── cache.go ├── redis.go ├── redis_suite_test.go └── redis_test.go ├── retrieveHelper ├── app.go └── user.go ├── stringHelper ├── stringHelper.go ├── stringHelper_suite_test.go └── stringHelper_test.go ├── timeHelper ├── timeHelper.go ├── timeHelper_suite_test.go └── timeHelper_test.go ├── upload └── image.go ├── userHelper └── user.go ├── validation ├── validation.go ├── validation_suite_test.go └── validation_test.go └── viper └── viper.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .webassets-cache 3 | .DS_Store 4 | *.log 5 | *.pid 6 | *.bak 7 | *.css~ 8 | *.swp 9 | *.zip 10 | *.coverprofile 11 | node_modules 12 | *.private 13 | .vendor 14 | vendor 15 | vendor/* 16 | gin-bin 17 | *.legacy 18 | .vscode -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch", 6 | "type": "go", 7 | "request": "launch", 8 | "mode": "debug", 9 | "remotePath": "", 10 | "port": 2345, 11 | "host": "127.0.0.1", 12 | "program": "${workspaceRoot}", 13 | "env": {}, 14 | "args": [], 15 | "showLog": true 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the offical Golang image to create a build artifact. 2 | FROM golang:1.20-alpine as builder 3 | 4 | RUN apk update && apk upgrade && \ 5 | apk --update add git make bash build-base 6 | 7 | # RUN apt-get update && apt-get upgrade 8 | # RUN apt-get -y install git make bash 9 | 10 | # Set destination for COPY 11 | WORKDIR /app 12 | 13 | # Download Go modules 14 | COPY go.mod go.sum ./ 15 | RUN go mod download 16 | 17 | # Set PKG_CONFIG_PATH for libvips 18 | # ENV PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig 19 | # RUN pkg-config --libs vips 20 | 21 | COPY . . 22 | 23 | # Build the command inside the container. 24 | # RUN CGO_ENABLED=0 GOOS=linux go build -v -o app main.go # with this option, I got 'executor failed' error 25 | RUN go build -v -o backend main.go 26 | 27 | # Distribution 28 | FROM alpine:latest 29 | 30 | RUN apk update && apk upgrade && \ 31 | apk --update --no-cache add tzdata && \ 32 | mkdir /app 33 | 34 | WORKDIR /app 35 | 36 | # EXPOSE 9090 37 | 38 | COPY --from=builder /app /app 39 | 40 | CMD ["/app/backend"] 41 | 42 | # Use a Docker multi-stage build to create a lean production image. 43 | # FROM gcr.io/distroless/base-debian11 44 | 45 | # RUN apt-get update && apt-get upgrade && \ 46 | # apt-get install -y libvips-dev --no-install-recommends \ 47 | # mkdir /app 48 | 49 | # COPY --from=builder /app/ . 50 | 51 | # Run the service binary. 52 | # CMD ["/app"] 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015 JoongSeob Vito Kim 2 | All Rights Reserved. 3 | 4 | MIT LICENSE 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /api/route.go: -------------------------------------------------------------------------------- 1 | // @APIVersion 1.0.0 2 | // @Title Goyangi API 3 | // @Description Goyangi API usually works as expected. But sometimes its not true 4 | // @Contact api@contact.me 5 | // @TermsOfServiceUrl http://google.com/ 6 | // @License BSD 7 | // @LicenseUrl http://opensource.org/licenses/BSD-2-Clause 8 | // @SubApi Authentication [/authentications] 9 | // @SubApi Users [/users] 10 | // @SubApi Oauth [/oauth] 11 | // @SubApi Roles [/roles] 12 | // @SubApi Articles [/articles] 13 | // @SubApi Upload [/upload] 14 | // @SubApi Commands [/commands] 15 | package api 16 | 17 | import ( 18 | "github.com/gin-gonic/gin" 19 | "github.com/spf13/viper" 20 | 21 | v1 "github.com/dorajistyle/goyangi/api/v1" 22 | ) 23 | 24 | // RouteAPI contains router groups for API 25 | func RouteAPI(parentRoute *gin.Engine) { 26 | 27 | route := parentRoute.Group(viper.GetString("api.url")) 28 | { 29 | v1.Users(route) 30 | v1.Roles(route) 31 | v1.Authentications(route) 32 | v1.Articles(route) 33 | v1.Upload(route) 34 | v1.Commands(route) 35 | v1.Oauth(route) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /api/v1/authentication.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | 6 | "github.com/dorajistyle/goyangi/api/response" 7 | "github.com/dorajistyle/goyangi/service/userService" 8 | "github.com/dorajistyle/goyangi/util/log" 9 | ) 10 | 11 | // @Title Authentications 12 | // @Description Authentications's router group. 13 | func Authentications(parentRoute *gin.RouterGroup) { 14 | route := parentRoute.Group("/authentications") 15 | route.POST("", createUserAuthentication) 16 | route.DELETE("", deleteUserAuthentication) 17 | } 18 | 19 | // @Title createUserAuthentication 20 | // @Description Create a user session. 21 | // @Accept json 22 | // @Param loginEmail formData string true "User email." 23 | // @Param loginPassword formData string true "User password." 24 | // @Success 201 {object} response.BasicResponse "User authentication created" 25 | // @Failure 401 {object} response.BasicResponse "Password incorrect" 26 | // @Failure 404 {object} response.BasicResponse "User is not found" 27 | // @Resource /authentications 28 | // @Router /authentications [post] 29 | func createUserAuthentication(c *gin.Context) { 30 | status, err := userService.CreateUserAuthentication(c) 31 | messageTypes := &response.MessageTypes{OK: "login.done", 32 | Unauthorized: "login.error.passwordIncorrect", 33 | NotFound: "login.error.userNotFound"} 34 | messages := &response.Messages{OK: "User logged in successfully."} 35 | log.Debugf("header : %s", c.Request) 36 | response.JSON(c, status, messageTypes, messages, err) 37 | } 38 | 39 | // @Title deleteUserAuthentication 40 | // @Description Delete a user session. 41 | // @Accept json 42 | // @Success 200 {object} response.BasicResponse "User logged out successfully" 43 | // @Resource /authentications 44 | // @Router /authentications [delete] 45 | func deleteUserAuthentication(c *gin.Context) { 46 | status, err := userService.ClearCookie(c) 47 | messageTypes := &response.MessageTypes{OK: "logout.done"} 48 | messages := &response.Messages{OK: "User logged out successfully."} 49 | response.JSON(c, status, messageTypes, messages, err) 50 | } 51 | -------------------------------------------------------------------------------- /api/v1/commands.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/dorajistyle/goyangi/db" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | // @Title Commands 9 | // @Description Commands' router group. It contains command API. 10 | func Commands(parentRoute *gin.RouterGroup) { 11 | route := parentRoute.Group("/commands") 12 | route.GET("migrate", migrate) 13 | } 14 | 15 | func migrate(c *gin.Context) { 16 | db.Migrate() 17 | } -------------------------------------------------------------------------------- /backend.dockerfile: -------------------------------------------------------------------------------- 1 | # Use the offical Golang image to create a build artifact. 2 | FROM golang:1.20-alpine as builder 3 | 4 | RUN apk update && apk upgrade && \ 5 | apk --update add git make bash build-base 6 | 7 | # RUN apt-get update && apt-get upgrade 8 | # RUN apt-get -y install git make bash 9 | 10 | # Set destination for COPY 11 | WORKDIR /app 12 | 13 | # Download Go modules 14 | COPY go.mod go.sum ./ 15 | RUN go mod download 16 | 17 | # Set PKG_CONFIG_PATH for libvips 18 | # ENV PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig 19 | # RUN pkg-config --libs vips 20 | 21 | COPY . . 22 | 23 | # Build the command inside the container. 24 | # RUN CGO_ENABLED=0 GOOS=linux go build -v -o app main.go # with this option, I got 'executor failed' error 25 | RUN go build -v -o goyangi-backend main.go 26 | 27 | # Distribution 28 | FROM golang:1.20-alpine as prod 29 | 30 | RUN apk update && apk upgrade && \ 31 | apk --update --no-cache add tzdata && \ 32 | mkdir /app 33 | 34 | WORKDIR /app 35 | 36 | # EXPOSE 9090 37 | 38 | COPY --from=builder /app /app 39 | 40 | ENTRYPOINT ["/app/goyangi-backend"] 41 | 42 | # Use a Docker multi-stage build to create a lean production image. 43 | # FROM gcr.io/distroless/base-debian11 44 | 45 | # RUN apt-get update && apt-get upgrade && \ 46 | # apt-get install -y libvips-dev --no-install-recommends \ 47 | # mkdir /app 48 | 49 | # COPY --from=builder /app/ . 50 | 51 | # Run the service binary. 52 | # CMD ["/app"] 53 | -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | # App 2 | app: 3 | name: Goyangi 4 | environment: DEVELOPMENT 5 | 6 | api: 7 | hostURL: "http://localhost" 8 | url: "api/v1" 9 | 10 | # Gin 11 | gin: 12 | port: 8080 13 | mode: debug 14 | 15 | # Database 16 | database: 17 | type: postgres 18 | # host: 127.0.0.1 # when you directly run 'go run main.go' 19 | host: host.docker.internal # for docker 20 | port: 5432 21 | user: postgres 22 | password: 1234 23 | database: goyangi_dev 24 | options: "sslmode=disable" 25 | 26 | # db table public fields 27 | publicFields: 28 | user: "id, username, md5, description, created_at, liking_count, liked_count" 29 | 30 | # db table select pagination 31 | pagination: 32 | article: 10 33 | liking: 5 34 | liked: 5 35 | comment: 5 36 | 37 | # db table select order 38 | order: 39 | article: "created_at desc" 40 | liking: "created_at desc" 41 | liked: "created_at desc" 42 | comment: "created_at desc" 43 | 44 | 45 | # Redis 46 | redis: 47 | addr: "172.19.102.44" 48 | port: "6379" 49 | 50 | # Upload 51 | upload: 52 | path: 53 | local: "/tmp/upload/" 54 | S3Image: "images/" 55 | target: "LOCAL" # LOCAL | S3 56 | bucket: "TEST" # TEST | PRODUCTION 57 | timeout: 30 58 | 59 | auth: 60 | tokenExpiration: 1000 # Unit: day 61 | 62 | aws: 63 | accessKeyID: "" 64 | secretAccessKey: "" 65 | s3: 66 | region: "" 67 | bucket: 68 | prefix: "" 69 | name: "" 70 | testBucket: 71 | prefix: "" 72 | name: "" 73 | staticBucket: 74 | name: "" 75 | elasticbeanstalk: 76 | region: "" 77 | appName: "" 78 | environment: 79 | id: "" 80 | name: "" 81 | s3: 82 | bucket: 83 | name: "" 84 | cloudfrontURL: "" 85 | 86 | 87 | # Email 88 | email: 89 | send: false # true | false 90 | from: "" 91 | testTo: "" 92 | host: "" 93 | username: "" 94 | password: "" 95 | port: 465 96 | timeout: 10 # seconds 97 | 98 | # Image 99 | image: 100 | default: 101 | width: 500 102 | height: 500 103 | large: 104 | width: 1920 105 | height: 1080 106 | medium: 107 | width: 1024 108 | height: 768 109 | thumbnail: 110 | width: 340 111 | height: 270 112 | 113 | # Log 114 | log: 115 | access: 116 | filepath: "log/access.txt" 117 | maxSize: 5 # megabytes 118 | maxBackups: 7 119 | maxAge: 30 # days 120 | error: 121 | filepath: "log/error.json" 122 | maxSize: 5 # megabytes 123 | maxBackups: 7 124 | maxAge: 30 # days 125 | 126 | # Oauth 127 | oauth: 128 | google: 129 | id: "" 130 | secret: "" 131 | github: 132 | id: "" 133 | secret: "" 134 | facebook: 135 | id: "" 136 | secret: "" 137 | twitter: 138 | id: "" 139 | secret: "" 140 | linkedin: 141 | id: "" 142 | secret: "" 143 | kakao: 144 | id: "" 145 | secret: "" 146 | naver: 147 | id: "" 148 | secret: "" 149 | yahoo: 150 | id: "" 151 | secret: "" 152 | 153 | # JWT 154 | jwt: 155 | client: 156 | method: HMAC256 # HMAC256 | RSA256 157 | key: 158 | private: "secretRSA" # secret 159 | public: "secretRSAPUBLIC" 160 | 161 | server: 162 | method: HMAC256 # HMAC256 | RSA256 163 | expirationHour: 24 # Hours 164 | key: 165 | private: > # secret 166 | -----BEGIN RSA PRIVATE KEY----- 167 | -----END RSA PRIVATE KEY----- 168 | public: > 169 | -----BEGIN PUBLIC KEY----- 170 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /db/gorm.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/dorajistyle/goyangi/util/log" 5 | "github.com/spf13/viper" 6 | 7 | // _ "github.com/go-sql-driver/mysql" 8 | "github.com/jinzhu/gorm" 9 | _ "github.com/lib/pq" 10 | ) 11 | 12 | var ORM *gorm.DB 13 | 14 | // GormInit init gorm ORM. 15 | func GormInit() { 16 | appEnvironmnet := viper.GetString("app.environment") 17 | dbType := viper.GetString("database.type") 18 | dbHost := viper.GetString("database.host") 19 | dbPort := viper.GetString("database.port") 20 | dbUser := viper.GetString("database.user") 21 | dbPassword := viper.GetString("database.password") 22 | dbDatabase := viper.GetString("database.database") 23 | dbOptions := viper.GetString("database.options") 24 | 25 | var databaseDSL string 26 | 27 | switch dbType { 28 | case "postgres": 29 | databaseDSL = "host=" + dbHost + " port=" + dbPort + " dbname=" + dbDatabase + " " + dbOptions + " user=" + dbUser + " password=" + dbPassword 30 | case "mysql": 31 | databaseDSL = dbUser + ":" + dbPassword + "@tcp(" + dbHost + ":" + dbPort + ")/" + dbDatabase + "?" + dbOptions 32 | 33 | } 34 | println(databaseDSL) 35 | db, err := gorm.Open(dbType, databaseDSL) 36 | 37 | db.DB() 38 | 39 | // Then you could invoke `*sql.DB`'s functions with it 40 | db.DB().Ping() 41 | db.DB().SetMaxIdleConns(10) 42 | db.DB().SetMaxOpenConns(100) 43 | 44 | // Disable table name's pluralization 45 | // db.SingularTable(true) 46 | if appEnvironmnet == "DEVELOPMENT" { 47 | db.LogMode(true) 48 | } 49 | log.CheckError(err) 50 | ORM = db 51 | } 52 | -------------------------------------------------------------------------------- /db/init_db.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE goyangi; 2 | CREATE DATABASE goyangi_dev; 3 | CREATE DATABASE goyangi_test; -------------------------------------------------------------------------------- /db/migration.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "time" 5 | "github.com/dorajistyle/goyangi/model" 6 | ) 7 | 8 | func Migrate() { 9 | MigrateUserAndRole() 10 | ORM.AutoMigrate(&model.Article{}, &model.CommentList{}, &model.Comment{}, &model.LikingList{}, &model.LikedList{}, &model.Image{}, &model.Tag{}, &model.Link{}, &model.App{}, &model.ExpiredTokenLog{}) 11 | ORM.AutoMigrate(&model.File{}, &model.Language{}) 12 | ORM.AutoMigrate(&model.UsersFollowers{}, &model.Connection{}) 13 | } 14 | 15 | func MigrateUserAndRole() { 16 | var adminRole model.Role 17 | var userRole model.Role 18 | 19 | hasRole := ORM.HasTable(&model.Role{}) 20 | hasUser := ORM.HasTable(&model.User{}) 21 | 22 | if !hasRole { 23 | ORM.CreateTable(&model.Role{}) 24 | adminRole.Name = "admin" 25 | adminRole.Description = "administrator" 26 | ORM.Create(&adminRole) 27 | userRole.Name = "user" 28 | userRole.Description = "ordinary user" 29 | ORM.Create(&userRole) 30 | } 31 | 32 | if !hasUser { 33 | ORM.CreateTable(&model.User{}) 34 | var user model.User 35 | user.Username = "admin" 36 | user.Email = "admin@goyangi.github.io" 37 | user.Password = "$2a$10$voqxhv08H2eWHbLJo2rEeO1GwGlg8ZLW3Y8348aqe0XBqVgEZxGOu" // password 38 | user.Name = "Goyangi" 39 | user.Birthday = time.Now() 40 | user.Gender = 2 41 | user.Md5 = "10d17498672e2dd040e8c0cf5a337a61" 42 | user.Activation = true 43 | user.Token = "168355cf5b6d31827c694260ab24e3bc3e990290ca94c7c30c6489ae1c1f212c" 44 | user.TokenExpiration = time.Now().Add(time.Hour * 24 * 365 * 100) 45 | ORM.Create(&user) 46 | ORM.Model(&user).Association("Roles").Append(adminRole) 47 | ORM.Model(&user).Association("Roles").Append(userRole) 48 | } 49 | } -------------------------------------------------------------------------------- /docker-compose.override.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | proxy: 4 | ports: 5 | - "80:80" 6 | command: 7 | # Enable Docker in Traefik, so that it reads labels from Docker services 8 | - --providers.docker 9 | # Add a constraint to only use services with the label for this stack 10 | # from the env var TRAEFIK_TAG 11 | - --providers.docker.constraints=Label(`traefik.constraint-label-stack`, `localhost`) 12 | # Do not expose all Docker services, only the ones explicitly exposed 13 | - --providers.docker.exposedbydefault=false 14 | # Disable Docker Swarm mode for local development 15 | # - --providers.docker.swarmmode 16 | # Enable the access log, with HTTP requests 17 | - --accesslog 18 | # Enable the Traefik log, for configurations and errors 19 | - --log 20 | # Enable the Dashboard and API 21 | - --api 22 | # Enable the Dashboard and API in insecure mode for local development 23 | - --api.insecure=true 24 | labels: 25 | - traefik.enable=true 26 | - traefik.http.routers.goyangi-traefik-public-http.rule=Host(`localhost`) 27 | - traefik.http.services.goyangi-traefik-public.loadbalancer.server.port=80 28 | 29 | db: 30 | ports: 31 | - "5432:5432" 32 | 33 | pgadmin: 34 | ports: 35 | - "5050:5050" 36 | 37 | backend: 38 | environment: 39 | - SERVER_HOST=http://localhost 40 | depends_on: 41 | - db 42 | build: 43 | dockerfile: backend.dockerfile 44 | target: prod 45 | args: 46 | INSTALL_DEV: ${INSTALL_DEV-true} 47 | labels: 48 | - traefik.enable=true 49 | - traefik.constraint-label-stack=localhost 50 | - traefik.http.routers.goyangi-backend-http.rule=PathPrefix(`/api`) || PathPrefix(`/swagger`) 51 | - traefik.http.services.goyangi-backend.loadbalancer.server.port=80 52 | 53 | frontend: 54 | build: 55 | context: ./frontend/vuejs 56 | args: 57 | FRONTEND_ENV: dev 58 | labels: 59 | - traefik.enable=true 60 | - traefik.constraint-label-stack=localhost 61 | - traefik.http.routers.goyangi-frontend-http.rule=PathPrefix(`/`) 62 | - traefik.http.services.goyangi-frontend.loadbalancer.server.port=80 63 | 64 | networks: 65 | traefik-public: 66 | # For local dev, don't expect an external Traefik network 67 | external: false 68 | -------------------------------------------------------------------------------- /document/images/goyangi-howitworks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dorajistyle/goyangi/dffa276e98f80ad1c5fd97cb5339c8a844491725/document/images/goyangi-howitworks.png -------------------------------------------------------------------------------- /document/images/goyangi-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dorajistyle/goyangi/dffa276e98f80ad1c5fd97cb5339c8a844491725/document/images/goyangi-stack.png -------------------------------------------------------------------------------- /document/images/goyangi-vuejs-board-kr.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dorajistyle/goyangi/dffa276e98f80ad1c5fd97cb5339c8a844491725/document/images/goyangi-vuejs-board-kr.JPG -------------------------------------------------------------------------------- /document/images/goyangi-vuejs-board.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dorajistyle/goyangi/dffa276e98f80ad1c5fd97cb5339c8a844491725/document/images/goyangi-vuejs-board.JPG -------------------------------------------------------------------------------- /document/images/goyangi-vuejs-home.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dorajistyle/goyangi/dffa276e98f80ad1c5fd97cb5339c8a844491725/document/images/goyangi-vuejs-home.JPG -------------------------------------------------------------------------------- /document/packages/backend.md: -------------------------------------------------------------------------------- 1 | ### Packages 2 | * [golang.org/x/crypto/bcrypt](https://godoc.org/golang.org/x/crypto/bcrypt)[[code](https://github.com/golang/crypto/)], It implements Provos and Mazières's bcrypt adaptive hashing algorithm. 3 | * [gorilla/securecookie](http://www.gorillatoolkit.org/pkg/securecookie)[[code](https://github.com/gorilla/securecookie)], It encodes and decodes authenticated and optionally encrypted cookie values. 4 | * [gin-gonic/gin](https://gin-gonic.github.io/gin/)[[code](https://github.com/gin-gonic/gin)], Gin is a web framework written in Golang. If you need performance and good productivity, you will love Gin. 5 | * [go-sql-driver/mysql](http://godoc.org/github.com/go-sql-driver/mysql)[[code](https://github.com/go-sql-driver/mysql)], A MySQL-Driver for Go's database/sql package 6 | * [jinzhu/gorm](https://godoc.org/github.com/jinzhu/gorm)[[code](https://github.com/jinzhu/gorm)], The fantastic ORM library for Golang, aims to be developer friendly. 7 | * [uber-go/zap](https://github.com/uber-go/zap)[[code](https://github.com/uber-go/zap)], Blazing fast, structured, leveled logging in Go. 8 | * [onsi/ginkgo](http://onsi.github.io/ginkgo/)[[code](https://github.com/onsi/ginkgo/)], BDD Testing Framework for Go. 9 | * [onsi/gomega](http://onsi.github.io/gomega/)[[code](https://github.com/onsi/gomega)], Ginkgo's Preferred Matcher Library 10 | * [goamz/goamz](http://godoc.org/github.com/goamz/goamz)[[code](https://github.com/goamz/goamz)], It enables Go programs to interact with Amazon Web Services. 11 | * [disintegration/gift](http://godoc.org/github.com/disintegration/gift)[[code](https://github.com/disintegration/gift)], It provides a set of useful image processing filters. 12 | * [gopkg.in/gomail.v1](https://gopkg.in/gomail.v1)[[code](https://github.com/go-gomail/gomail/tree/v1)], A simple and powerful Go package to send emails. 13 | * [golang.org/x/oauth2](https://godoc.org/golang.org/x/oauth2)[[code](https://github.com/golang/oauth2)], It contains a client implementation for OAuth 2.0 spec. 14 | * [nicksnyder/go-i18n](https://godoc.org/github.com/nicksnyder/go-i18n)[[code](https://github.com/nicksnyder/go-i18n)], It is a Go package and a command that can be used to translate Go programs into multiple languages. 15 | * [DAddYE/vips](https://godoc.org/github.com/DAddYE/vips)[[code](https://github.com/DAddYE/vips)], It is powered by the blazingly fast libvips image processing library, originally created in 1989 at Birkbeck College and currently maintained by JohnCupitt. 16 | * [natefinch/lumberjack.v2](https://gopkg.in/natefinch/lumberjack.v2)[[code](https://github.com/natefinch/lumberjack/tree/v2.0)], It is a Go package for writing logs to rolling files. 17 | * [codegangsta/cli](https://godoc.org/github.com/codegangsta/cli)[[code](https://github.com/codegangsta/cli)], It is simple, fast, and fun package for building command line apps in Go. 18 | * [codegangsta/gin](https://godoc.org/github.com/codegangsta/gin)[[code](https://github.com/codegangsta/gin)], Live reload utility for Go web servers 19 | * [nitrous-io/goop](https://godoc.org/github.com/nitrous-io/goop)[[code](https://github.com/nitrous-io/goop)], A dependency manager for Go (golang), inspired by Bundler. 20 | * []()[[code]()], 21 | -------------------------------------------------------------------------------- /document/packages/frontend.md: -------------------------------------------------------------------------------- 1 | ## CanJS - Legacy 2 | 3 | ### Libraries 4 | * [Canjs](http://canjs.com/), Client-side JavaScript Framework. 5 | * [RequireJS](http://requirejs.org/), JavaScript file and module loader. 6 | * [RequireJS Optimizer](http://requirejs.org/docs/optimization.html), Optimization tool of RequireJS. 7 | * [RequireJS plugins](https://github.com/millermedeiros/requirejs-plugins), Small set of plugins for RequireJS. 8 | * [Initializr](http://www.initializr.com/), HTML5 templates generator. 9 | * [UIkit](http://getuikit.com/), A lightweight and modular front-end framework for developing fast and powerful web interfaces. 10 | * [Hammer](http://hammerjs.github.io/), A open-source library that can recognize gestures made by touch, mouse and pointerEvents. 11 | * [Moment.js](http://momentjs.com/), Parse, validate, manipulate, and display dates in JavaScript. 12 | * [mustache](http://mustache.github.io/), Logic-less templates. 13 | * [i18next](http://i18next.com/), JavaScript translating toolkit. 14 | * [loglevel](https://github.com/pimterry/loglevel), Minimal lightweight simple logging for JavaScript. 15 | * [typeahead.js](https://github.com/twitter/typeahead.js), It is a fast and fully-featured autocomplete library. 16 | * [spin.js](http://fgnass.github.io/spin.js/), It dynamically creates spinning activity indicators that can be used as resolution-independent replacement for AJAX loading GIFs. 17 | * [Placeholders.js](http://jamesallardice.github.io/Placeholders.js/), It is a JavaScript polyfill for the HTML5 placeholder attribute. 18 | * [Magnific Popup](http://dimsemenov.com/plugins/magnific-popup/), A responsive lightbox & dialog script with focus on performance and providing best experience for user with any device. 19 | * [Trumbowyg](http://alex-d.github.io/Trumbowyg/), A light, translatable and customisable jQuery plugin. 20 | * [jQuery](http://jquery.com/), It is a fast, small, and feature-rich JavaScript library. 21 | * [jQuery BBQ](http://benalman.com/projects/jquery-bbq-plugin/), It leverages the HTML5 hashchange event to allow simple, yet powerful bookmarkable #hash history. 22 | * [jQuery File Upload](https://blueimp.github.io/jQuery-File-Upload/), File Upload widget with multiple file selection, drag&drop support, progress bars, validation and preview images, audio and video for jQuery. 23 | * [googlemaps-amd](https://github.com/aerisweather/googlemaps-amd) Google Maps AMD Loader Plugin. 24 | 25 | ### Management 26 | * [Bower](http://bower.io/), A package manager for the web 27 | * [Gulp](http://gulpjs.com/), Automate and enhance your workflow. 28 | * [gulp-util](https://github.com/gulpjs/gulp-util), Utilities for gulp plugins. 29 | * [gulp-uncss](https://github.com/ben-eb/gulp-uncss), Remove unused CSS selectors. 30 | * [yargs](https://github.com/bcoe/yargs), The modern, pirate-themed successor to optimist. 31 | * [gulp-concat](https://github.com/wearefractal/gulp-concat), Streaming concat middleware for gulp. 32 | * [gulp-uglify](https://github.com/terinjokes/gulp-uglify), Minify files with UglifyJS 33 | * [gulp-replace](https://github.com/lazd/gulp-replace), A string replace plugin for gulp. 34 | * [gulp-sass](https://github.com/dlmanning/gulp-sass), SASS plugin for gulp 35 | * [gulp-minify-css](https://github.com/jonathanepollack/gulp-minify-css), A Gulp plugin that minifies css with clean-css. 36 | * [gulp-autoprefixer](https://github.com/sindresorhus/gulp-autoprefixer), Prefix CSS. 37 | * [merge-stream](https://github.com/grncdr/merge-stream), Merge multiple streams into one interleaved stream. 38 | * [run-sequence](https://github.com/OverZealous/run-sequence), Run a series of dependent gulp tasks in order. 39 | * [Can-Compile](http://daffl.github.io/can-compile/), Compiles CanJS EJS and Mustache views into a single JavaScript file. 40 | -------------------------------------------------------------------------------- /form/common.go: -------------------------------------------------------------------------------- 1 | package form 2 | 3 | // RetrieveListForm used when retrieving any kind of list. 4 | type RetrieveListForm struct { 5 | CurrentPage int `form:"currentPage" binding:"required"` 6 | } 7 | -------------------------------------------------------------------------------- /frontend/vuejs/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /frontend/vuejs/Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 0, "build-stage", based on Node.js, to build and compile the frontend 2 | # inspired by followings 3 | # * https://snyk.io/blog/10-best-practices-to-containerize-nodejs-web-applications-with-docker/ 4 | # * https://dev.to/otomato_io/how-to-optimize-production-docker-images-running-nodejs-with-yarn-504b 5 | # * https://github.com/pyb4430/full-stack-fastapi-postgresql/blob/master/%7B%7Bcookiecutter.project_slug%7D%7D/frontend/Dockerfile 6 | FROM node:16-alpine as build-stage 7 | 8 | WORKDIR /app 9 | 10 | COPY package*.json /app/ 11 | 12 | COPY yarn.lock /app/ 13 | 14 | RUN yarn install --immutable --immutable-cache --check-cache . 15 | 16 | COPY ./ /app/ 17 | 18 | ARG FRONTEND_ENV=production 19 | 20 | ENV VUE_APP_ENV=${FRONTEND_ENV} 21 | 22 | RUN yarn build 23 | 24 | 25 | FROM nginx:1.24-alpine 26 | 27 | COPY --from=build-stage /app/dist/ /usr/share/nginx/html 28 | 29 | COPY ./nginx.conf /etc/nginx/conf.d/default.conf 30 | COPY ./nginx-backend-not-found.conf /etc/nginx/extra-conf.d/backend-not-found.conf -------------------------------------------------------------------------------- /frontend/vuejs/README.md: -------------------------------------------------------------------------------- 1 | # vuejs 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | yarn run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | yarn run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | yarn run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /frontend/vuejs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /frontend/vuejs/nginx-backend-not-found.conf: -------------------------------------------------------------------------------- 1 | # location /api { 2 | # return 404; 3 | # } 4 | # location /docs { 5 | # return 404; 6 | # } 7 | # location /redoc { 8 | # return 404; 9 | # } -------------------------------------------------------------------------------- /frontend/vuejs/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | location / { 5 | root /usr/share/nginx/html/; 6 | index index.html index.htm; 7 | try_files $uri $uri/ /index.html =404; 8 | } 9 | 10 | include /etc/nginx/extra-conf.d/*.conf; 11 | } -------------------------------------------------------------------------------- /frontend/vuejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "goyangi", 3 | "version": "1.13.0", 4 | "description": "Goyangi is a web foundation to help you getting started with a new web project based on Golang.", 5 | "author": "Joongseob Vito Kim", 6 | "private": true, 7 | "scripts": { 8 | "serve": "vue-cli-service serve", 9 | "build": "vue-cli-service build", 10 | "lint": "vue-cli-service lint" 11 | }, 12 | "dependencies": { 13 | "@panter/vue-i18next": "^0.5.1", 14 | "axios": "^0.16.2", 15 | "buefy": "^0.7.0", 16 | "bulma": "^0.5.0", 17 | "bulma-divider": "^0.2.0", 18 | "bulma-extensions": "^6.2.7", 19 | "core-js": "^2.6.5", 20 | "humps": "^2.0.1", 21 | "i18next": "^8.4.3", 22 | "jquery": "^3.2.1", 23 | "vee-validate": "^2.1.0-beta.11", 24 | "vue": "^2.6.10", 25 | "vue-meta": "^1.0.5", 26 | "vue-moment": "^4.0.0", 27 | "vue-router": "^2.6.0", 28 | "vuex": "^2.3.1", 29 | "vuex-router-sync": "^4.2.0" 30 | }, 31 | "devDependencies": { 32 | "@vue/cli-plugin-babel": "^3.6.0", 33 | "@vue/cli-plugin-eslint": "^3.6.0", 34 | "@vue/cli-service": "^3.6.0", 35 | "babel-eslint": "^10.0.1", 36 | "eslint": "^5.16.0", 37 | "eslint-plugin-vue": "^5.0.0", 38 | "vue-template-compiler": "^2.5.21" 39 | }, 40 | "eslintConfig": { 41 | "root": true, 42 | "env": { 43 | "node": true 44 | }, 45 | "extends": [ 46 | "plugin:vue/essential", 47 | "eslint:recommended" 48 | ], 49 | "rules": { 50 | "arrow-parens": 0, 51 | "generator-star-spacing": 0, 52 | "no-console": "off", 53 | "no-debugger": "off" 54 | }, 55 | "parserOptions": { 56 | "parser": "babel-eslint" 57 | } 58 | }, 59 | "postcss": { 60 | "plugins": { 61 | "autoprefixer": {} 62 | } 63 | }, 64 | "browserslist": [ 65 | "> 1%", 66 | "last 2 versions", 67 | "not ie <= 8" 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /frontend/vuejs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dorajistyle/goyangi/dffa276e98f80ad1c5fd97cb5339c8a844491725/frontend/vuejs/public/favicon.ico -------------------------------------------------------------------------------- /frontend/vuejs/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | goyangi 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 22 | 23 |
24 |
25 |
26 | 29 |
30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 | 40 |
41 |
42 |
43 | 44 | 45 |
46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /frontend/vuejs/src/api/ArticleAPI.js: -------------------------------------------------------------------------------- 1 | import BaseAPI from './BaseAPI' 2 | 3 | class ArticleAPI extends BaseAPI { 4 | constructor () { 5 | super('/articles') 6 | } 7 | 8 | create (formData) { 9 | // for (var pair of formData.entries()) { 10 | // console.log(`API ${pair[0]}: ${pair[1]}`) 11 | // } 12 | return super.post('', null, formData) 13 | } 14 | 15 | update (id, formData) { 16 | console.log(`ID : ${formData.get('id')}`) 17 | for (var pair of formData.entries()) { 18 | console.log(`API ${pair[0]}: ${pair[1]}`) 19 | } 20 | return super.put(id, null, formData) 21 | } 22 | 23 | delete (id) { 24 | return super.delete(id, null) 25 | } 26 | 27 | list (currentPage = 1) { 28 | return super.get('', {currentPage: currentPage}) 29 | } 30 | 31 | retrieve (id) { 32 | return super.get(id) 33 | } 34 | 35 | listComments(id, currentPage = 1) { 36 | return super.get(`${id}/comments`, {currentPage: currentPage}) 37 | } 38 | 39 | createComment (id, formData) { 40 | return super.post(`${id}/comments`, null, formData) 41 | } 42 | 43 | updateComment (id, commentId, formData) { 44 | return super.put(`${id}/comments/${commentId}`, null, formData) 45 | } 46 | 47 | deleteComment (id, commentId) { 48 | return super.delete(`${id}/comments/${commentId}`, null) 49 | } 50 | 51 | listLikings(id, currentPage = 1) { 52 | return super.get(`${id}/likings`, {currentPage: currentPage}) 53 | } 54 | 55 | createLiking (id, formData) { 56 | return super.post(`${id}/likings`, null, formData) 57 | } 58 | 59 | deleteLiking (id, userId) { 60 | return super.delete(`${id}/likings/${userId}`, null) 61 | } 62 | 63 | 64 | 65 | } 66 | 67 | export default ArticleAPI 68 | -------------------------------------------------------------------------------- /frontend/vuejs/src/api/AuthenticationAPI.js: -------------------------------------------------------------------------------- 1 | import BaseAPI from './BaseAPI' 2 | 3 | class AuthenticationAPI extends BaseAPI { 4 | constructor () { 5 | super('/authentications') 6 | } 7 | 8 | login (cred) { 9 | return this.post('', cred) 10 | } 11 | 12 | } 13 | 14 | export default AuthenticationAPI 15 | -------------------------------------------------------------------------------- /frontend/vuejs/src/api/UserAPI.js: -------------------------------------------------------------------------------- 1 | import BaseAPI from './BaseAPI' 2 | 3 | class UserAPI extends BaseAPI { 4 | constructor () { 5 | super('/users') 6 | } 7 | 8 | create (cred) { 9 | return this.post('', cred) 10 | } 11 | 12 | } 13 | 14 | export default UserAPI 15 | -------------------------------------------------------------------------------- /frontend/vuejs/src/api/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import baseURI from '../settings/api_base' 3 | import { Toast } from 'buefy/dist/components/toast' 4 | import i18next from 'i18next' 5 | 6 | axios.interceptors.response.use((response) => { 7 | // Do something with response data 8 | if (response.data) { 9 | Toast.open({ 10 | duration: 5000, 11 | message: i18next.t(response.data.messageType), 12 | type: 'is-success'}) 13 | } 14 | return response 15 | }, (error) => { 16 | // Do something with response error 17 | if (error.response) { 18 | // The request was made and the server responded with a status code 19 | // that falls out of the range of 2xx 20 | if (error.response.data) { 21 | Toast.open({ 22 | duration: 2000, 23 | message: i18next.t(error.response.data.messageType), 24 | type: 'is-danger'}) 25 | } else { 26 | if (error.response.status) { 27 | console.log('status : ', error.response.status) 28 | } 29 | if (error.response.headers) { 30 | console.log('header : ' + JSON.stringify(error.response.headers)) 31 | } 32 | } 33 | } else if (error.request) { 34 | // The request was made but no response was received 35 | // `error.request` is an instance of XMLHttpRequest in the browser and an instance of 36 | // http.ClientRequest in node.js 37 | console.log('request : ' + error.request) 38 | } else { 39 | // Something happened in setting up the request that triggered an Error 40 | console.log('Error', error.message) 41 | console.log('config error : ' + JSON.stringify(error.config)) 42 | } 43 | 44 | // return Promise.reject(error) 45 | }) 46 | 47 | export default (method, uri, params = null) => { 48 | if (!method) { 49 | console.error('API function call requires method argument') 50 | return 51 | } 52 | 53 | if (!uri) { 54 | console.error('API function call requires uri argument') 55 | return 56 | } 57 | 58 | var url = baseURI + uri 59 | return axios({ method, url, params }) 60 | } 61 | -------------------------------------------------------------------------------- /frontend/vuejs/src/assets/goyangi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dorajistyle/goyangi/dffa276e98f80ad1c5fd97cb5339c8a844491725/frontend/vuejs/src/assets/goyangi.jpg -------------------------------------------------------------------------------- /frontend/vuejs/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dorajistyle/goyangi/dffa276e98f80ad1c5fd97cb5339c8a844491725/frontend/vuejs/src/assets/logo.png -------------------------------------------------------------------------------- /frontend/vuejs/src/components/articles/List.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 86 | 87 | 88 | 91 | -------------------------------------------------------------------------------- /frontend/vuejs/src/components/comments/Form.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | -------------------------------------------------------------------------------- /frontend/vuejs/src/components/comments/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | -------------------------------------------------------------------------------- /frontend/vuejs/src/components/likings/index.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | -------------------------------------------------------------------------------- /frontend/vuejs/src/layouts/BasicLayout.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 92 | 93 | 106 | -------------------------------------------------------------------------------- /frontend/vuejs/src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import { sync } from 'vuex-router-sync' 5 | import App from './views/App' 6 | import router from './router' 7 | import store from './store' 8 | import Buefy from 'buefy' 9 | import 'buefy/dist/buefy.css' 10 | import 'bulma-divider' 11 | import Moment from 'vue-moment' 12 | import i18next from 'i18next' 13 | import i18nOption from './settings/i18next' 14 | import VueI18Next from '@panter/vue-i18next' 15 | import VeeValidate, { Validator } from 'vee-validate' 16 | import ko from 'vee-validate/dist/locale/ko' 17 | import $ from 'jquery' 18 | 19 | Vue.use({ 20 | install: function(Vue){ 21 | Vue.prototype.$jQuery = $; // you'll have this.$jQuery anywhere in your vue project 22 | } 23 | }) 24 | Vue.use(Buefy) 25 | Vue.use(Moment) 26 | Vue.use(VueI18Next) 27 | sync(store, router) 28 | 29 | var lang = $('html').attr('lang') 30 | 31 | lang !== undefined 32 | ? i18nOption.lng = lang 33 | : i18nOption.lng = 'en' 34 | i18next.init(i18nOption) 35 | const i18n = new VueI18Next(i18next) 36 | 37 | Vue.config.productionTip = false 38 | 39 | Vue.use(VeeValidate, { 40 | locale: 'ko' 41 | }) 42 | Validator.localize('ko', ko) 43 | 44 | /* eslint-disable no-new */ 45 | new Vue({ 46 | el: '#app', 47 | router: router, 48 | store: store, 49 | i18n: i18n, 50 | render: h => h(App) 51 | }) 52 | 53 | // Check local storage to handle refreshes 54 | if (window.localStorage) { 55 | var localUserString = window.localStorage.getItem('user') || 'null' 56 | var localUser = JSON.parse(localUserString) 57 | 58 | if (localUser && store.state.user !== localUser) { 59 | store.commit('SET_USER', localUser) 60 | store.commit('SET_IS_AUTHENTICATED', window.localStorage.getItem('isAuthenticated')) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /frontend/vuejs/src/router/articles/index.js: -------------------------------------------------------------------------------- 1 | import Articles from '@/views/articles' 2 | import ArticleList from '@/components/articles/List' 3 | import ArticleItem from '@/components/articles/Item' 4 | import ArticleForm from '@/components/articles/Form' 5 | 6 | export default [ 7 | { 8 | path: '/articles', 9 | component: Articles, 10 | children: [ 11 | { 12 | path: '', 13 | component: ArticleList, 14 | props: (route) => ({ currentPage: route.query.currentPage }) 15 | }, 16 | { 17 | path: 'new', 18 | component: ArticleForm, 19 | meta: { requiresAuth: true }, 20 | props: {action: 'new'} 21 | }, 22 | { 23 | path: ':articleId', 24 | component: ArticleItem, 25 | props: true 26 | }, 27 | { 28 | path: ':articleId/edit', 29 | component: ArticleForm, 30 | props: {action: 'edit'} 31 | } 32 | ] 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /frontend/vuejs/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import BasicLayout from '@/layouts/BasicLayout' 4 | import Home from '@/views/Home' 5 | import usersRoute from './users' 6 | import articlesRoute from './articles' 7 | import store from '@/store' 8 | Vue.use(Router) 9 | 10 | const router = new Router({ 11 | mode: 'history', 12 | routes: [ 13 | { 14 | path: '/', 15 | component: BasicLayout, 16 | children: [ 17 | { 18 | path: '', 19 | component: Home 20 | }, ...usersRoute, 21 | ...articlesRoute 22 | ] 23 | } 24 | ] 25 | }) 26 | 27 | router.beforeEach((to, from, next) => { 28 | if (to.matched.some(record => record.meta.requiresAuth) && !store.state.authentications.isAuthenticated) return next("/login") 29 | next() 30 | }) 31 | 32 | export default router 33 | 34 | 35 | -------------------------------------------------------------------------------- /frontend/vuejs/src/router/users/index.js: -------------------------------------------------------------------------------- 1 | import Login from '@/views/users/Login' 2 | import Registration from '@/views/users/Registration' 3 | 4 | export default [ 5 | { 6 | path: '/login', 7 | component: Login 8 | }, { 9 | path: '/registration', 10 | component: Registration 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /frontend/vuejs/src/settings/api_base.js: -------------------------------------------------------------------------------- 1 | const HOST = '//localhost' 2 | const API_BASE_URI = HOST + '/api/v1' 3 | 4 | export default API_BASE_URI 5 | -------------------------------------------------------------------------------- /frontend/vuejs/src/settings/i18next.js: -------------------------------------------------------------------------------- 1 | const en = require('@/locales/en/translation.json') 2 | const ko = require('@/locales/ko/translation.json') 3 | 4 | export default {debug: true, 5 | fallbackLng: 'en', 6 | resources: { 7 | en: { translation: en }, 8 | ko: { translation: ko } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/actions.js: -------------------------------------------------------------------------------- 1 | // import types from './action_types' 2 | // import api from '../api' 3 | // export default { 4 | // login ({ commit }, creds) { 5 | // commit(types.LOGIN) // show spinner 6 | // return api('post', '/authentications', creds) 7 | // .then((response) => { 8 | // if (response) { 9 | // // console.log('response', response) 10 | 11 | // localStorage.setItem('token', 'JWT') 12 | // commit(types.LOGIN_SUCCESS) 13 | // } 14 | // }) 15 | // // return new Promise(resolve => { 16 | // // setTimeout(() => { 17 | // // localStorage.setItem('token', 'JWT') 18 | // // commit(types.LOGIN_SUCCESS) 19 | // // resolve() 20 | // // }, 1000) 21 | // // }) 22 | // }, 23 | // logout ({ commit }) { 24 | // localStorage.removeItem('token') 25 | // commit(types.LOGOUT) 26 | // } 27 | // } 28 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/getters.js: -------------------------------------------------------------------------------- 1 | // export default { 2 | // currentLocale: state => { 3 | // return state.locale 4 | // } 5 | 6 | // } 7 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import authentications from './modules/authentications' 4 | import users from './modules/users' 5 | import articles from './modules/articles' 6 | import languages from './modules/languages' 7 | 8 | Vue.use(Vuex) 9 | 10 | const store = new Vuex.Store({ 11 | modules: { 12 | authentications: authentications, 13 | users: users, 14 | articles: articles, 15 | languages: languages 16 | } 17 | }) 18 | 19 | export default store 20 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/modules/articles/getters.js: -------------------------------------------------------------------------------- 1 | export default { 2 | articleList: state => state.articleList, 3 | canWrite: (_, rootGetters) => userId => userId == rootGetters.currentUserId, 4 | article: state => state.article, 5 | } 6 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/modules/articles/index.js: -------------------------------------------------------------------------------- 1 | import actions from './actions' 2 | import getters from './getters' 3 | import mutations from './mutations' 4 | 5 | const state = { 6 | articleAPI: null, 7 | pending: false, 8 | callingAPI: false, 9 | searching: '', 10 | articleList: {}, 11 | article: {} 12 | } 13 | 14 | export default { 15 | state, 16 | actions, 17 | getters, 18 | mutations 19 | } 20 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/modules/articles/mutations.js: -------------------------------------------------------------------------------- 1 | import ArticleAPI from '../../../api/ArticleAPI' 2 | 3 | import { articleTypes } from '../../mutation-types' 4 | 5 | export default { 6 | [articleTypes.SET_API] (state) { 7 | if (state.articleAPI === null) { 8 | state.articleAPI = new ArticleAPI() 9 | } 10 | }, 11 | [articleTypes.PENDING] (state) { 12 | state.pending = true 13 | }, 14 | [articleTypes.DONE] (state) { 15 | state.pending = false 16 | }, 17 | [articleTypes.NEW_ARTICLE] (state) { 18 | state.article = {} 19 | }, 20 | [articleTypes.SET_ARTICLE_LIST] (state, response) { 21 | state.articleList = response.articleList 22 | }, 23 | [articleTypes.SET_ARTICLE] (state, response) { 24 | console.log(`SET_ARTICLE response : ${JSON.stringify(response)}`) 25 | state.article = response.article 26 | console.log(`state.article.id : ${state.article.id}`) 27 | }, 28 | [articleTypes.SET_ARTICLE_COMMENT_LIST] (state, response) { 29 | let comments = state.article.commentList.comments 30 | state.article.commentList = response.commentList 31 | if(state.article.commentList.currentPage !== 1 && state.article.commentList.comments != null) { 32 | state.article.commentList.comments = comments.concat(state.article.commentList.comments) 33 | } 34 | }, 35 | [articleTypes.SET_ARTICLE_LIKING_LIST] (state, response) { 36 | state.article.likingList = response.likingList 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/modules/authentications/actions.js: -------------------------------------------------------------------------------- 1 | import {authenticationTypes} from '../../mutation-types' 2 | 3 | export default { 4 | initAuthenticationAPI ({commit}) { 5 | commit(authenticationTypes.SET_API) 6 | }, 7 | login ({commit, state, dispatch}, creds) { 8 | dispatch('initAuthenticationAPI') 9 | commit(authenticationTypes.LOGIN) // show spinner 10 | 11 | state 12 | .authenticationAPI 13 | .login(creds) 14 | .then((response) => { 15 | if (response) { 16 | localStorage.setItem('isAuthenticated', true) 17 | commit(authenticationTypes.LOGIN_SUCCESS) 18 | } 19 | }) 20 | // return new Promise(resolve => { 21 | // setTimeout(() => { 22 | // localStorage.setItem('token', 'JWT') 23 | // commit(types.LOGIN_SUCCESS) 24 | // resolve() 25 | // }, 1000) 26 | // }) 27 | }, 28 | logout ({ commit }) { 29 | localStorage.removeItem('isAuthenticated') 30 | commit(authenticationTypes.LOGOUT) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/modules/authentications/getters.js: -------------------------------------------------------------------------------- 1 | export default { 2 | isAuthenticated: state => state.isAuthenticated, 3 | currentUserId: state => state.currentUser.id 4 | } 5 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/modules/authentications/index.js: -------------------------------------------------------------------------------- 1 | import actions from './actions' 2 | import getters from './getters' 3 | import mutations from './mutations' 4 | 5 | const state = { 6 | isAuthenticated: !!localStorage.getItem('isAuthenticated'), 7 | authenticationAPI: null, 8 | pending: false, 9 | callingAPI: false, 10 | searching: '', 11 | currentUser: {id: 1} 12 | } 13 | 14 | export default { 15 | state, 16 | actions, 17 | getters, 18 | mutations 19 | } 20 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/modules/authentications/mutations.js: -------------------------------------------------------------------------------- 1 | import AuthenticationAPI from '../../../api/AuthenticationAPI' 2 | 3 | import { authenticationTypes } from '../../mutation-types' 4 | 5 | export default { 6 | [authenticationTypes.SET_API] (state) { 7 | if (state.authenticationAPI === null) { 8 | state.authenticationAPI = new AuthenticationAPI() 9 | } 10 | }, 11 | [authenticationTypes.LOGIN] (state) { 12 | state.pending = true 13 | }, 14 | [authenticationTypes.LOGIN_SUCCESS] (state) { 15 | state.isAuthenticated = true 16 | state.pending = false 17 | }, 18 | [authenticationTypes.LOGOUT] (state) { 19 | state.isAuthenticated = false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/modules/languages/actions.js: -------------------------------------------------------------------------------- 1 | import {languageTypes} from '../../mutation-types' 2 | export default { 3 | changeLocale ({ commit }, locale) { 4 | console.log('locale to : '+ locale) 5 | commit(languageTypes.CHANGE_LOCALE, locale) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/modules/languages/getters.js: -------------------------------------------------------------------------------- 1 | export default { 2 | currentLocale: state => state.locale 3 | } 4 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/modules/languages/index.js: -------------------------------------------------------------------------------- 1 | import actions from './actions' 2 | import getters from './getters' 3 | import mutations from './mutations' 4 | 5 | const state = { 6 | locale: 'en' 7 | } 8 | 9 | export default { 10 | state, 11 | actions, 12 | getters, 13 | mutations 14 | } 15 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/modules/languages/mutations.js: -------------------------------------------------------------------------------- 1 | import i18next from 'i18next' 2 | import { languageTypes } from '../../mutation-types' 3 | 4 | export default { 5 | [languageTypes.CHANGE_LOCALE] (state, locale) { 6 | state.locale = locale 7 | i18next.changeLanguage(locale) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/modules/users/actions.js: -------------------------------------------------------------------------------- 1 | import {userTypes} from '../../mutation-types' 2 | 3 | export default { 4 | initUserAPI ({commit}) { 5 | commit(userTypes.SET_API) 6 | }, 7 | createUser ({commit, state, dispatch}, creds) { 8 | dispatch('initUserAPI') 9 | commit(userTypes.CREATE_USER) // show spinner 10 | 11 | state 12 | .userAPI 13 | .create(creds) 14 | .then((response) => { 15 | if (response) { 16 | localStorage.setItem('token', 'JWT') 17 | commit(userTypes.CREATE_USER_SUCCESS) 18 | } 19 | }) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/modules/users/getters.js: -------------------------------------------------------------------------------- 1 | export default { 2 | createdUser: state => state.createdUser 3 | } 4 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/modules/users/index.js: -------------------------------------------------------------------------------- 1 | import actions from './actions' 2 | import getters from './getters' 3 | import mutations from './mutations' 4 | 5 | const state = { 6 | createdUser: !!localStorage.getItem('token'), 7 | userAPI: null, 8 | pending: false, 9 | callingAPI: false, 10 | searching: '', 11 | user: null 12 | } 13 | 14 | export default { 15 | state, 16 | actions, 17 | getters, 18 | mutations 19 | } 20 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/modules/users/mutations.js: -------------------------------------------------------------------------------- 1 | import UserAPI from '../../../api/UserAPI' 2 | 3 | import { userTypes } from '../../mutation-types' 4 | 5 | export default { 6 | [userTypes.SET_API] (state) { 7 | if (state.userAPI === null) { 8 | state.userAPI = new UserAPI() 9 | } 10 | }, 11 | [userTypes.CREATE_USER] (state) { 12 | state.pending = true 13 | }, 14 | [userTypes.CREATE_USER_SUCCESS] (state) { 15 | state.createdUser = true 16 | state.pending = false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const authenticationTypes = { 2 | SET_API: 'SET_API', 3 | LOGIN: 'LOGIN', 4 | LOGIN_SUCCESS: 'LOGIN_SUCCESS', 5 | LOGOUT: 'LOGOUT' 6 | } 7 | 8 | export const userTypes = { 9 | SET_API: 'SET_API', 10 | CREATE_USER: 'CREATE_USER', 11 | CREATE_USER_SUCCESS: 'CREATE_USER_SUCCESS' 12 | } 13 | 14 | export const articleTypes = { 15 | SET_API: 'SET_API', 16 | PENDING: 'PENDING', 17 | DONE: 'DONE', 18 | SET_ARTICLE_LIST: 'SET_ARTICLE_LIST', 19 | NEW_ARTICLE: 'NEW_ARTICLE', 20 | SET_ARTICLE: 'SET_ARTICLE', 21 | SET_ARTICLE_COMMENT_LIST: 'SET_ARTICLE_COMMENT_LIST', 22 | SET_ARTICLE_LIKING_LIST: 'SET_ARTICLE_LIKING_LIST' 23 | } 24 | 25 | export const languageTypes = { 26 | CHANGE_LOCALE: 'CHANGE_LOCALE' 27 | } 28 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/mutations.js: -------------------------------------------------------------------------------- 1 | // import types from './action_types' 2 | // import i18next from 'i18next' 3 | 4 | // export default { 5 | 6 | // [types.CHANGE_LOCALE] (state, locale) { 7 | // state.locale = locale 8 | // i18next.changeLanguage(locale) 9 | // }, 10 | // TOGGLE_LOADING (state) { 11 | // state.callingAPI = !state.callingAPI 12 | // }, 13 | // TOGGLE_SEARCHING (state) { 14 | // state.searching = (state.searching === '') ? 'loading' : '' 15 | // }, 16 | // SET_USER (state, user) { 17 | // state.user = user 18 | // }, 19 | // SET_IS_AUTHENTICATED (state, status) { 20 | // state.isAuthenticated = status 21 | // } 22 | // } 23 | -------------------------------------------------------------------------------- /frontend/vuejs/src/store/state.js: -------------------------------------------------------------------------------- 1 | // import baseURI from '../settings/api_base' 2 | 3 | // export default { 4 | // isAuthenticated: !!localStorage.getItem('token'), 5 | // locale: 'en', 6 | // callingAPI: false, 7 | // searching: '', 8 | // baseURI: baseURI, 9 | // user: null, 10 | // token: null, 11 | // userInfo: { 12 | // messages: [{1: 'test', 2: 'test'}], 13 | // notifications: [], 14 | // tasks: [] 15 | // } 16 | // } 17 | -------------------------------------------------------------------------------- /frontend/vuejs/src/views/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/vuejs/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 22 | 23 | 24 | 43 | -------------------------------------------------------------------------------- /frontend/vuejs/src/views/articles/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | 18 | 37 | -------------------------------------------------------------------------------- /frontend/vuejs/src/views/users/Login.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 63 | 64 | 65 | 68 | -------------------------------------------------------------------------------- /frontend/vuejs/src/views/users/Registration.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 75 | 76 | 77 | 80 | -------------------------------------------------------------------------------- /log/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/dorajistyle/goyangi/db" 8 | "github.com/dorajistyle/goyangi/script" 9 | "github.com/dorajistyle/goyangi/server" 10 | "github.com/spf13/viper" 11 | "github.com/urfave/cli" 12 | ) 13 | 14 | func init() { 15 | viper.AddConfigPath(".") 16 | viper.SetConfigType("yaml") 17 | 18 | // switch os.Getenv("ENV") { 19 | // case "DEVELOPMENT": 20 | // viper.SetConfigName(".env.dev") 21 | // case "TEST": 22 | // viper.SetConfigName(".env.test") 23 | // case "PRODUCTION": 24 | // viper.SetConfigName(".env.prod") 25 | // } 26 | 27 | viper.SetConfigName("config.yml") 28 | 29 | viper.AutomaticEnv() 30 | 31 | if err := viper.ReadInConfig(); err != nil { 32 | log.Fatalln("Fatal error config file", err) 33 | } 34 | } 35 | 36 | func main() { 37 | app := cli.NewApp() 38 | app.Name = "Goyangi script tool" 39 | app.Usage = "run scripts!" 40 | app.Version = "0.1.0" 41 | 42 | app.Author = "https://github.com/dorajistyle(JoongSeob Vito Kim)" 43 | app.Commands = script.Commands() 44 | app.Action = func(c *cli.Context) { 45 | println("Run Server.") 46 | println(viper.GetString("app.name")) 47 | 48 | server.Run() 49 | } 50 | println(viper.GetString("database.type")) 51 | db.GormInit() 52 | 53 | app.Run(os.Args) 54 | } 55 | -------------------------------------------------------------------------------- /model/app.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type App struct { 8 | Id uint `json:"id"` 9 | Name string `json:"name",sql:"size:255"` 10 | Key string `json:"name",sql:"size:255"` 11 | Token string `json:"token"` 12 | TokenExpiration time.Time `json:"tokenExperiation"` 13 | } 14 | 15 | type ExpiredTokenLog struct{ 16 | UserId uint `json:"userId"` 17 | AccessedAt time.Time `json:"activatedAt"` 18 | } -------------------------------------------------------------------------------- /model/article.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Article is a article model. 8 | type Article struct { 9 | Id uint `json:"id"` 10 | Title string `json:"title",sql:"size:255"` 11 | Url string `json:"url",sql:"size:512"` 12 | Content string `json:"content"` 13 | UserId uint `json:"userId"` 14 | Author User `json:"author"` 15 | ReferralId uint `json:"referralId"` 16 | ReferralUserId uint `json:"referralUserId"` 17 | CategoryId int `json:"categoryId"` 18 | PrevId uint `json:"prevId"` 19 | NextId uint `json:"nextId"` 20 | LikingCount int `json:"likingCount"` 21 | CommentCount int `json:"commentCount"` 22 | SharingCount int `json:"sharingCount"` 23 | ImageName string `json:"imageName",sql:"size:512"` 24 | ThumbnailName string `json:"thumbnailName",sql:"size:512"` 25 | Activate bool `json:"active"` 26 | CreatedAt time.Time `json:"createdAt"` 27 | UpdatedAt time.Time `json:"updatedAt"` 28 | DeletedAt *time.Time `json:"deletedAt"` 29 | Comments []Comment `gorm:"many2many:articles_comments;association_autoupdate:false;association_autocreate:false"` 30 | CommentList CommentList `json:"commentList"` 31 | Likings []User `gorm:"many2many:articles_users;association_autoupdate:false;association_autocreate:false"` 32 | LikingList LikingList `json:"likingList"` 33 | } 34 | 35 | // ArticleList is list that contains articles and meta. 36 | type ArticleList struct { 37 | Articles []Article `json:"articles"` 38 | Category int `json:"category"` 39 | HasPrev bool `json:"hasPrev"` 40 | HasNext bool `json:"hasNext"` 41 | Count int `json:"count"` 42 | CurrentPage int `json:"currentPage"` 43 | PerPage int `json:"perPage"` 44 | } -------------------------------------------------------------------------------- /model/common.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // omit is the bool type for omitting a field of struct. 8 | type omit bool 9 | 10 | // CommentList is list that contains comments and meta. 11 | type CommentList struct { 12 | Comments []Comment `json:"comments"` 13 | HasPrev bool `json:"hasPrev"` 14 | HasNext bool `json:"hasNext"` 15 | Count int `json:"count"` 16 | CurrentPage int `json:"currentPage"` 17 | } 18 | 19 | // Comment is a comment model. 20 | type Comment struct { 21 | Id uint `json:"id"` 22 | Content string `json:"content"` 23 | UserId uint `json:"userId"` 24 | LikingCount int `json:"likingCount"` 25 | CreatedAt time.Time `json:"createdAt"` 26 | UpdatedAt time.Time `json:"updatedAt"` 27 | DeletedAt *time.Time `json:"deletedAt"` 28 | User User `json:"user"` 29 | } 30 | 31 | // LikingList is list that contains likings and meta. 32 | type LikingList struct { 33 | Likings []User `json:"likings"` 34 | HasPrev bool `json:"hasPrev"` 35 | HasNext bool `json:"hasNext"` 36 | Count int `json:"count"` 37 | CurrentPage int `json:"currentPage"` 38 | IsLiked bool `json:"isLiked"` 39 | } 40 | 41 | // LikedList is list that contains liked and meta. 42 | type LikedList struct { 43 | Liked []User `json:"liked"` 44 | HasPrev bool `json:"hasPrev"` 45 | HasNext bool `json:"hasNext"` 46 | Count int `json:"count"` 47 | CurrentPage int `json:"currentPage"` 48 | 49 | } 50 | 51 | // Image is a image model. 52 | type Image struct { 53 | Id uint `json:"id"` 54 | Kind int `json:"kind"` 55 | Large string `json:"large"` 56 | Medium string `json:"medium"` 57 | Thumbnail string `json:"thumbnail"` 58 | CreatedAt time.Time `json:"createdAt"` 59 | } 60 | 61 | // Tag is a tag model. 62 | type Tag struct { 63 | Id uint `json:"id"` 64 | Name string `json:"name",sql:"size:255"` 65 | } 66 | 67 | // Link is a link model. 68 | type Link struct { 69 | Id uint `json:"id"` 70 | Kind int `json:"kind"` 71 | Name string `json:"title",sql:"size:255"` 72 | Url string `json:"url",sql:"size:512"` 73 | CreatedAt time.Time `json:"createdAt"` 74 | Icon string `json:"icon",sql:"size:255"` 75 | } 76 | -------------------------------------------------------------------------------- /model/file.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // File is a file model that contains meta. 8 | type File struct { 9 | Id uint `json:"id"` 10 | UserId uint `json:"userId"` 11 | Name string `json:"name",sql:"size:255"` 12 | Size int `json:"size",sql:"size:255"` 13 | CreatedAt time.Time `json:"createdAt"` 14 | } 15 | -------------------------------------------------------------------------------- /model/language.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // Language is a language model. 4 | type Language struct { 5 | Id uint `json:"id"` 6 | Name string `json:"name"` 7 | } 8 | -------------------------------------------------------------------------------- /model/role.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // Role is a role model for user permission. 4 | type Role struct { 5 | Id uint `json:"id"` 6 | Name string `json:"name",sql:"size:255"` 7 | Description string `json:"description",sql:"size:255"` 8 | } 9 | -------------------------------------------------------------------------------- /readme_ko.md: -------------------------------------------------------------------------------- 1 | # Goyangi 2 | 3 | 고양이는 고언어 기반의 새로운 웹 프로젝트 개발을 위한 토대입니다. SPA나 모바일 어플리케이션을 제작하기에 좋습니다. 4 | 고언어 패키지들을 이용한 대규모 웹 어플리케이션 예제로도 좋습니다. 5 | [Goyangi](http://en.wikipedia.org/wiki/goyangi)의 뜻은 글자 그대로 고양이입니다. 6 | 7 | ## 왜 고양이를 만들었나요? 8 | 모든것을 갖춘 고 언어용 웹 프레임웍이 몇 있습니다.([Beego](http://beego.me/), [Revel](http://revel.github.io/)...). 그렇지만 그 프레임웍들은 너무 많은 것을 갖추고 있습니다. 9 | 10 | _______________________ 11 | ## 사용권 12 | 13 | Goyangi는 [MIT license](./LICENSE)를 따릅니다. 14 | -------------------------------------------------------------------------------- /script/commands.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/urfave/cli" 7 | "github.com/dorajistyle/goyangi/server" 8 | ) 9 | 10 | func Commands() []cli.Command { 11 | return []cli.Command{ 12 | // Init application 13 | { 14 | Name: "init", 15 | Usage: "Init application(go packages)", 16 | Action: func(c *cli.Context) { 17 | commands := InitApp() 18 | RunScript(commands) 19 | println("Init script done.") 20 | }, 21 | }, 22 | // Generate API document 23 | { 24 | Name: "generateAPI", 25 | Aliases: []string{"ga"}, 26 | Usage: "Generate API document using swagger", 27 | Action: func(c *cli.Context) { 28 | // go|swagger|asciidoc|markdown|confluence 29 | format := "asciidoc" 30 | if len(c.Args()) > 0 { 31 | format = c.Args()[0] 32 | } 33 | commands := GenerateAPI(format) 34 | RunScript(commands) 35 | formatRegex, _ := regexp.Compile("^(go|swagger|asciidoc|markdown|confluence)$") 36 | if formatRegex.MatchString(format) { 37 | println("API document generated. You can find document at ./document/") 38 | } else { 39 | println("Invalid -format specified. Must be one of go|swagger|asciidoc|markdown|confluence.") 40 | } 41 | }, 42 | }, 43 | // Run server 44 | { 45 | Name: "server", 46 | Aliases: []string{"s"}, 47 | Usage: "Run server", 48 | Action: func(c *cli.Context) { 49 | db := "" 50 | if len(c.Args()) > 0 { 51 | db = c.Args()[0] 52 | } 53 | commands := Server(db) 54 | RunScript(commands) 55 | server.Run() 56 | println("Server is running now.") 57 | }, 58 | }, 59 | // Run test 60 | { 61 | Name: "test", 62 | Aliases: []string{"t"}, 63 | Usage: "Run test using Ginkgo", 64 | Action: func(c *cli.Context) { 65 | ci := "" 66 | if len(c.Args()) > 0 { 67 | ci = c.Args()[0] 68 | } 69 | commands := Test(ci) 70 | RunScript(commands) 71 | println("Test script done.") 72 | }, 73 | }, 74 | } // end []cli.Command 75 | } // end Commands() 76 | -------------------------------------------------------------------------------- /script/constant.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | // Constants for script commands. 4 | const ( 5 | Bash = "/bin/bash" 6 | GoRun = "go run " 7 | ) 8 | -------------------------------------------------------------------------------- /script/generateAPI.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | func GenerateAPI(format string) []string { 4 | commands := make([]string, 2) 5 | prefix := "swagger -apiPackage=\"github.com/dorajistyle/goyangi/api/v1\" -mainApiFile=\"github.com/dorajistyle/goyangi/api/route.go\" -format=\"" + format + "\" " 6 | switch format { 7 | case "go": 8 | fallthrough 9 | case "swagger": 10 | commands = append(commands, prefix+"-output=\"document/\"") 11 | case "asciidoc": 12 | commands = append(commands, prefix+"-output=\"document/API.adoc\"") 13 | commands = append(commands, "asciidoctor -a icons -a toc2 -a stylesheet=github.css -a stylesdir=./stylesheets document/API.adoc") 14 | case "markdown": 15 | commands = append(commands, prefix+"-output=\"document/API.md\"") 16 | case "confluence": 17 | commands = append(commands, prefix+"-output=\"document/API.confluence\"") 18 | } 19 | return commands 20 | } 21 | -------------------------------------------------------------------------------- /script/init.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | import ( 4 | "log" 5 | "os" 6 | ) 7 | 8 | func InitApp() []string { 9 | commands := make([]string, 8) 10 | dir, err := os.Getwd() 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | commands = append(commands, "SCRIPTPATH="+dir) 15 | commands = append(commands, "dep ensure") 16 | commands = append(commands, "cd \"$SCRIPTPATH/frontend/vuejs\"") 17 | commands = append(commands, "yarn") 18 | return commands 19 | } 20 | -------------------------------------------------------------------------------- /script/script.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "os/exec" 7 | "strings" 8 | "sync" 9 | ) 10 | 11 | func RunScript(commands []string) { 12 | entireScript := strings.NewReader(strings.Join(commands, "\n")) 13 | bash := exec.Command(Bash) 14 | stdin, _ := bash.StdinPipe() 15 | stdout, _ := bash.StdoutPipe() 16 | stderr, _ := bash.StderrPipe() 17 | 18 | wait := sync.WaitGroup{} 19 | wait.Add(3) 20 | go func() { 21 | io.Copy(stdin, entireScript) 22 | stdin.Close() 23 | wait.Done() 24 | }() 25 | go func() { 26 | io.Copy(os.Stdout, stdout) 27 | wait.Done() 28 | }() 29 | go func() { 30 | io.Copy(os.Stderr, stderr) 31 | wait.Done() 32 | }() 33 | 34 | bash.Start() 35 | wait.Wait() 36 | bash.Wait() 37 | } 38 | -------------------------------------------------------------------------------- /script/server.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | func Server(db string) []string { 4 | commands := make([]string, 2) 5 | switch db { 6 | case "mysql": 7 | commands = append(commands, "/usr/bin/mysqld_safe &") 8 | commands = append(commands, "sleep 10s") 9 | } 10 | // commands = append(commands, GoopGoRun+"server.go") 11 | return commands 12 | } 13 | -------------------------------------------------------------------------------- /script/test.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | func Test(ci string) []string { 4 | commands := make([]string, 3) 5 | command := "ginkgo -r --randomizeAllSpecs --randomizeSuites --failOnPending --trace" 6 | switch ci { 7 | case "travis": 8 | command = command + " --cover --race --compilers=2" 9 | } 10 | commands = append(commands, command) 11 | return commands 12 | } 13 | -------------------------------------------------------------------------------- /server/exec/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/dorajistyle/goyangi/server" 4 | 5 | func main() { 6 | server.Run() 7 | } 8 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/dorajistyle/goyangi/api" 7 | "github.com/spf13/viper" 8 | 9 | docs "github.com/dorajistyle/goyangi/docs" 10 | "github.com/dorajistyle/goyangi/util/log" 11 | "github.com/gin-contrib/cors" 12 | "github.com/gin-gonic/gin" 13 | "github.com/nicksnyder/go-i18n/i18n" 14 | swaggerfiles "github.com/swaggo/files" 15 | ginSwagger "github.com/swaggo/gin-swagger" 16 | ) 17 | 18 | func initI18N() { 19 | i18n.MustLoadTranslationFile("service/userService/locale/en-us.all.json") 20 | i18n.MustLoadTranslationFile("service/userService/locale/ko-kr.all.json") 21 | } 22 | 23 | func init() { 24 | 25 | log.Init(viper.GetString("app.environment")) 26 | initI18N() 27 | } 28 | 29 | // CORSMiddleware for CORS 30 | func CORSMiddleware() gin.HandlerFunc { 31 | corsConfig := cors.New(cors.Config{ 32 | AllowOrigins: []string{"http://localhost:8080", "http://localhost"}, 33 | AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, 34 | AllowHeaders: []string{"Origin", "access-control-allow-origin"}, 35 | ExposeHeaders: []string{"Content-Length"}, 36 | AllowCredentials: true, 37 | AllowOriginFunc: func(origin string) bool { 38 | return origin == "https://github.com" 39 | }, 40 | MaxAge: 12 * time.Hour, 41 | }) 42 | // corsConfig = cors.Default() 43 | return corsConfig 44 | } 45 | 46 | func Run() { 47 | r := gin.New() 48 | docs.SwaggerInfo.BasePath = "/" + viper.GetString("api.url") 49 | 50 | // Global middlewares 51 | // If use gin.Logger middlewares, it send duplicated request. 52 | switch viper.GetString("app.environment") { 53 | case "DEVELOPMENT": 54 | r.Use(gin.Logger()) 55 | case "TEST": 56 | r.Use(log.AccessLogger()) 57 | case "PRODUCTION": 58 | r.Use(log.AccessLogger()) 59 | } 60 | r.Use(gin.Recovery()) 61 | r.Use(CORSMiddleware()) 62 | 63 | api.RouteAPI(r) 64 | r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) 65 | 66 | // Listen and server on 0.0.0.0:3001 67 | // r.Run("localhost:3001") 68 | r.Run(":80") 69 | } 70 | -------------------------------------------------------------------------------- /service/articleService/comment.go: -------------------------------------------------------------------------------- 1 | package articleService 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | 7 | "github.com/dorajistyle/goyangi/db" 8 | "github.com/dorajistyle/goyangi/form" 9 | "github.com/dorajistyle/goyangi/model" 10 | "github.com/dorajistyle/goyangi/service/commentService" 11 | "github.com/dorajistyle/goyangi/util/log" 12 | "github.com/gin-gonic/gin" 13 | "github.com/gin-gonic/gin/binding" 14 | ) 15 | 16 | // UpdateArticleCommentCount updates article's comment count. 17 | func UpdateArticleCommentCount(article *model.Article) (int, error) { 18 | article.CommentCount = db.ORM.Model(article).Association("Comments").Count() 19 | if db.ORM.Save(article).Error != nil { 20 | return http.StatusInternalServerError, errors.New("Article comment's count is not updated.") 21 | } 22 | return http.StatusOK, nil 23 | } 24 | 25 | // CreateCommentOnArticle creates a comment to an article. 26 | func CreateCommentOnArticle(c *gin.Context) (int, error) { 27 | article := &model.Article{} 28 | status, err := commentService.CreateComment(c, article) 29 | if err != nil { 30 | return status, err 31 | } 32 | status, err = UpdateArticleCommentCount(article) 33 | if err != nil { 34 | return status, err 35 | } 36 | return http.StatusCreated, nil 37 | } 38 | 39 | // RetrieveCommentsOnArticles retrieve comments on an article. 40 | func RetrieveCommentsOnArticle(c *gin.Context) (model.CommentList, int, error) { 41 | var article model.Article 42 | var commentList model.CommentList 43 | var retrieveListForm form.RetrieveListForm 44 | 45 | articleId := c.Params.ByName("id") 46 | if db.ORM.First(&article, articleId).RecordNotFound() { 47 | return commentList, http.StatusNotFound, errors.New("Article is not found.") 48 | } 49 | bindErr := c.MustBindWith(&retrieveListForm, binding.Form) 50 | log.Debugf("[RetrieveCommentsOnArticle] bind error : %s\n", bindErr) 51 | if bindErr != nil { 52 | return commentList, http.StatusBadRequest, errors.New("Comments are not retrieved.") 53 | } 54 | log.Debugf("retrieveListForm : %v", retrieveListForm) 55 | commentList = commentService.RetrieveComments(article, retrieveListForm.CurrentPage) 56 | return commentList, http.StatusOK, nil 57 | } 58 | 59 | // UpdateCommentOnArticle updates a comment on an article. 60 | func UpdateCommentOnArticle(c *gin.Context) (int, error) { 61 | article := &model.Article{} 62 | status, err := commentService.UpdateComment(c, article) 63 | if err != nil { 64 | return status, err 65 | } 66 | status, err = UpdateArticleCommentCount(article) 67 | if err != nil { 68 | return status, err 69 | } 70 | return http.StatusOK, err 71 | } 72 | 73 | // DeleteCommentOnArticle deletes a comment from an article. 74 | func DeleteCommentOnArticle(c *gin.Context) (int, error) { 75 | article := &model.Article{} 76 | status, err := commentService.DeleteComment(c, article) 77 | if err != nil { 78 | return status, err 79 | } 80 | status, err = UpdateArticleCommentCount(article) 81 | if err != nil { 82 | return status, err 83 | } 84 | return http.StatusOK, err 85 | } 86 | -------------------------------------------------------------------------------- /service/articleService/form.go: -------------------------------------------------------------------------------- 1 | package articleService 2 | 3 | // ArticleFilter is a filter for retriving articles. 4 | type ArticleFilter struct { 5 | UserId int `form:"userId" json:"userId"` 6 | Categories []int `form:"categories" json:"categories"` 7 | CurrentPage int `form:"currentPage" json:"currentPage"` 8 | ArticlePerPage int `form:"articlePerPage" json:"articlePerPage"` 9 | } 10 | 11 | // ArticleForm is a form of article. 12 | type ArticleForm struct { 13 | Id uint `form:"id"` 14 | UserId uint `form:"userId"` 15 | CategoryId int `form:"categoryId"` 16 | Title string `form:"title" binding:"required"` 17 | Content string `form:"content" binding:"required"` 18 | Url string `form:"url"` 19 | ImageName string `form:"imageName"` 20 | ThumbnailName string `form:"thumbnailName"` 21 | } 22 | 23 | // ArticlesForm is used when creating or updating multiple Articles. 24 | type ArticlesForm struct { 25 | Articles []ArticleForm `form:"json" binding:"required"` 26 | } 27 | -------------------------------------------------------------------------------- /service/articleService/liking.go: -------------------------------------------------------------------------------- 1 | package articleService 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "github.com/dorajistyle/goyangi/db" 7 | "github.com/dorajistyle/goyangi/form" 8 | "github.com/dorajistyle/goyangi/model" 9 | "github.com/dorajistyle/goyangi/service/userService" 10 | "github.com/dorajistyle/goyangi/service/likingService" 11 | "github.com/dorajistyle/goyangi/util/log" 12 | "github.com/gin-gonic/gin" 13 | "github.com/gin-gonic/gin/binding" 14 | ) 15 | 16 | // UpdateArticleLikingCount updates a liking count on article. 17 | func UpdateArticleLikingCount(article *model.Article) (int, error) { 18 | article.LikingCount = db.ORM.Model(article).Association("Likings").Count() 19 | if db.ORM.Save(article).Error != nil { 20 | return http.StatusInternalServerError, errors.New("Article liking's count is not updated.") 21 | } 22 | return http.StatusOK, nil 23 | } 24 | 25 | // CreateLikingOnArticle creates a liking on article. 26 | func CreateLikingOnArticle(c *gin.Context) (int, error) { 27 | article := &model.Article{} 28 | status, err := likingService.CreateLiking(c, article) 29 | if err != nil { 30 | return status, err 31 | } 32 | status, err = UpdateArticleLikingCount(article) 33 | if err != nil { 34 | return status, err 35 | } 36 | return http.StatusOK, nil 37 | } 38 | 39 | // RetrieveLikingsOnArticles retrieves likings on article. 40 | func RetrieveLikingsOnArticles(c *gin.Context) (model.LikingList, int, error) { 41 | var article model.Article 42 | var likingList model.LikingList 43 | var retrieveListForm form.RetrieveListForm 44 | 45 | articleId := c.Params.ByName("id") 46 | log.Debugf("Liking params : %v", c.Params) 47 | 48 | bindErr := c.MustBindWith(&retrieveListForm, binding.Form) 49 | log.Debugf("[RetrieveLikingsOnArticles] bind error : %s\n", bindErr) 50 | if bindErr != nil { 51 | return likingList, http.StatusBadRequest, errors.New("Comments are not retrieved.") 52 | } 53 | 54 | 55 | log.Debugf("retrieveListForm %+v\n", retrieveListForm) 56 | if db.ORM.First(&article, articleId).RecordNotFound() { 57 | return likingList, http.StatusNotFound, errors.New("Article is not found.") 58 | } 59 | currentUser, _ := userService.CurrentUser(c) 60 | likingList = likingService.RetrieveLikings(article, currentUser.Id) 61 | // DEPRECATED likingMeta.SetLikingPageMeta(&likingList, currentPage, hasPrev, hasNext, article.LikingCount, currentUserlikedCount) 62 | 63 | return likingList, http.StatusOK, nil 64 | } 65 | 66 | // DeleteLikingOnArticle deletes liking on article. 67 | func DeleteLikingOnArticle(c *gin.Context) (int, error) { 68 | article := &model.Article{} 69 | status, err := likingService.DeleteLiking(c, article) 70 | if err != nil { 71 | return status, err 72 | } 73 | status, err = UpdateArticleLikingCount(article) 74 | if err != nil { 75 | return status, err 76 | } 77 | return http.StatusOK, nil 78 | } 79 | -------------------------------------------------------------------------------- /service/commentService/form.go: -------------------------------------------------------------------------------- 1 | package commentService 2 | 3 | // CreateCommentForm is used when creating a comment. 4 | type CreateCommentForm struct { 5 | Content string `form:"content" binding:"required"` 6 | UserId uint `form:"userId"` 7 | } 8 | 9 | // CommentForm is used when updating a comment. 10 | type CommentForm struct { 11 | CommentId uint `form:"commentId" binding:"required"` 12 | Content string `form:"content"` 13 | UserId uint `form:"userId" binding:"required"` 14 | } 15 | -------------------------------------------------------------------------------- /service/doc.go: -------------------------------------------------------------------------------- 1 | package service 2 | // Copyright 2015 JoongSeob Vito Kim. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | /* 7 | Package service - bussiness logics are contained in separated packages. 8 | 9 | HttpStatusCode 10 | 11 | Goyangi uses 11 http status code for bussiness logics. It's from net/http/status.go 12 | StatusOK = 200 13 | StatusCreated = 201 14 | StatusSeeOther = 303 15 | StatusNotModified = 304 16 | StatusBadRequest = 400 17 | StatusUnauthorized = 401 18 | StatusPaymentRequired = 402 19 | StatusForbidden = 403 20 | StatusNotFound = 404 21 | StatusMethodNotAllowed = 405 22 | StatusInternalServerError = 500 23 | */ 24 | -------------------------------------------------------------------------------- /service/likingService/form.go: -------------------------------------------------------------------------------- 1 | package likingService 2 | 3 | // CreateLikingForm is used when creating a liking. 4 | type CreateLikingForm struct { 5 | UserId uint `form:"userId" binding:"required"` 6 | ParentId uint `form:"parentId" binding:"required"` 7 | } 8 | -------------------------------------------------------------------------------- /service/oauthService/facebook.go: -------------------------------------------------------------------------------- 1 | package oauthService 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | 8 | "github.com/dorajistyle/goyangi/util/log" 9 | "github.com/dorajistyle/goyangi/util/modelHelper" 10 | "github.com/dorajistyle/goyangi/util/oauth2" 11 | "github.com/dorajistyle/goyangi/util/oauth2/facebook" 12 | "github.com/gin-gonic/gin" 13 | "github.com/gin-gonic/gin/binding" 14 | ) 15 | 16 | // FacebookURL return facebook auth url. 17 | func FacebookURL() (string, int) { 18 | return oauth2.OauthURL(facebook.Config), http.StatusOK 19 | } 20 | 21 | // SetFacebookUser set facebook user. 22 | func SetFacebookUser(response *http.Response) (*FacebookUser, error) { 23 | facebookUser := &FacebookUser{} 24 | defer response.Body.Close() 25 | body, err := ioutil.ReadAll(response.Body) 26 | if err != nil { 27 | return facebookUser, err 28 | } 29 | unmarshalErr := json.Unmarshal(body, &facebookUser) 30 | if unmarshalErr != nil { 31 | return facebookUser, unmarshalErr 32 | } 33 | log.Debugf("\nfacebookUser: %v\n", facebookUser) 34 | return facebookUser, nil 35 | } 36 | 37 | // OauthFacebook link connection and user. 38 | func OauthFacebook(c *gin.Context) (int, error) { 39 | var authResponse oauth2.AuthResponse 40 | var oauthUser OauthUser 41 | bindErr := c.MustBindWith(&authResponse, binding.Form) 42 | log.Debugf("bind error : %s\n", bindErr) 43 | log.Debugf("oauthRedirect form: %v", authResponse) 44 | response, token, err := oauth2.OauthRequest(facebook.RequestURL, facebook.Config, authResponse) 45 | if err != nil { 46 | return http.StatusInternalServerError, err 47 | } 48 | facebookUser, err := SetFacebookUser(response) 49 | modelHelper.AssignValue(&oauthUser, facebookUser) 50 | log.Debugf("\noauthUser item : %v", oauthUser) 51 | if err != nil { 52 | return http.StatusInternalServerError, err 53 | } 54 | status, err := LoginOrCreateOauthUser(c, &oauthUser, facebook.ProviderId, token) 55 | if err != nil { 56 | return status, err 57 | } 58 | return http.StatusSeeOther, nil 59 | } 60 | 61 | // RevokeFacebook revokes facebook oauth connection. 62 | func RevokeFacebook(c *gin.Context) (map[string]bool, int, error) { 63 | return RevokeOauth(c, facebook.ProviderId) 64 | } 65 | -------------------------------------------------------------------------------- /service/oauthService/github.go: -------------------------------------------------------------------------------- 1 | package oauthService 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | "strconv" 8 | 9 | "github.com/dorajistyle/goyangi/util/log" 10 | "github.com/dorajistyle/goyangi/util/modelHelper" 11 | "github.com/dorajistyle/goyangi/util/oauth2" 12 | "github.com/dorajistyle/goyangi/util/oauth2/github" 13 | "github.com/gin-gonic/gin" 14 | "github.com/gin-gonic/gin/binding" 15 | ) 16 | 17 | // GithubURL return github auth url. 18 | func GithubURL() (string, int) { 19 | return oauth2.OauthURL(github.Config), http.StatusOK 20 | } 21 | 22 | // SetGithubUser set github user. 23 | func SetGithubUser(response *http.Response) (*GithubUser, error) { 24 | githubUser := &GithubUser{} 25 | defer response.Body.Close() 26 | body, err := ioutil.ReadAll(response.Body) 27 | if err != nil { 28 | return githubUser, err 29 | } 30 | unmarshalErr := json.Unmarshal(body, &githubUser) 31 | if unmarshalErr != nil { 32 | return githubUser, unmarshalErr 33 | } 34 | log.Debugf("\ngithubUser: %v\n", githubUser) 35 | return githubUser, err 36 | } 37 | 38 | // OauthGithub link connection and user. 39 | func OauthGithub(c *gin.Context) (int, error) { 40 | var authResponse oauth2.AuthResponse 41 | var oauthUser OauthUser 42 | bindErr := c.MustBindWith(&authResponse, binding.Form) 43 | log.Debugf("bind error : %s\n", bindErr) 44 | log.Debugf("oauthRedirect form: %v", authResponse) 45 | response, token, err := oauth2.OauthRequest(github.RequestURL, github.Config, authResponse) 46 | if err != nil { 47 | log.Error("get response error", err) 48 | return http.StatusInternalServerError, err 49 | } 50 | githubUser, err := SetGithubUser(response) 51 | if err != nil { 52 | log.Error("SetGithubUser error", err) 53 | return http.StatusInternalServerError, err 54 | } 55 | modelHelper.AssignValue(&oauthUser, githubUser) 56 | oauthUser.ID = strconv.Itoa(githubUser.UserID) 57 | log.Debugf("\noauthUser item : %v", oauthUser) 58 | status, err := LoginOrCreateOauthUser(c, &oauthUser, github.ProviderId, token) 59 | if err != nil { 60 | log.Errorf("LoginOrCreateOauthUser error", err, "status", status) 61 | return status, err 62 | } 63 | return http.StatusSeeOther, nil 64 | } 65 | 66 | // RevokeGithub revokes github oauth connection. 67 | func RevokeGithub(c *gin.Context) (map[string]bool, int, error) { 68 | log.Debug("RevokeGithub performed") 69 | return RevokeOauth(c, github.ProviderId) 70 | } 71 | -------------------------------------------------------------------------------- /service/oauthService/google.go: -------------------------------------------------------------------------------- 1 | package oauthService 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | 8 | "github.com/dorajistyle/goyangi/util/log" 9 | "github.com/dorajistyle/goyangi/util/modelHelper" 10 | "github.com/dorajistyle/goyangi/util/oauth2" 11 | "github.com/dorajistyle/goyangi/util/oauth2/google" 12 | "github.com/gin-gonic/gin" 13 | "github.com/gin-gonic/gin/binding" 14 | ) 15 | 16 | // GoogleURL return google auth url. 17 | func GoogleURL() (string, int) { 18 | return oauth2.OauthURL(google.Config), http.StatusOK 19 | } 20 | 21 | // SetGoogleUser set google user. 22 | func SetGoogleUser(response *http.Response) (*GoogleUser, error) { 23 | googleUser := &GoogleUser{} 24 | log.Debugf("\nresponse: %v\n", response) 25 | defer response.Body.Close() 26 | body, err := ioutil.ReadAll(response.Body) 27 | if err != nil { 28 | return googleUser, err 29 | } 30 | unmarshalErr := json.Unmarshal(body, &googleUser) 31 | if unmarshalErr != nil { 32 | return googleUser, unmarshalErr 33 | } 34 | return googleUser, err 35 | } 36 | 37 | // OauthGoogle link connection and user. 38 | func OauthGoogle(c *gin.Context) (int, error) { 39 | log.Debugf("c.Request.URL : %s", c.Request.URL) 40 | var authResponse oauth2.AuthResponse 41 | var oauthUser OauthUser 42 | bindErr := c.MustBindWith(&authResponse, binding.Form) 43 | log.Debugf("bind error : %s\n", bindErr) 44 | log.Debugf("oauthRedirect form: %v", authResponse) 45 | response, token, err := oauth2.OauthRequest(google.RequestURL, google.Config, authResponse) 46 | if err != nil { 47 | return http.StatusInternalServerError, err 48 | } 49 | googleUser, err := SetGoogleUser(response) 50 | if err != nil { 51 | return http.StatusInternalServerError, err 52 | } 53 | modelHelper.AssignValue(&oauthUser, googleUser) 54 | oauthUser.Email = googleUser.Emails[0].Value 55 | oauthUser.ImageUrl = googleUser.Image.URL 56 | status, err := LoginOrCreateOauthUser(c, &oauthUser, google.ProviderId, token) 57 | if err != nil { 58 | return status, err 59 | } 60 | return http.StatusSeeOther, nil 61 | } 62 | 63 | // RevokeGoogle revokes google oauth connection. 64 | func RevokeGoogle(c *gin.Context) (map[string]bool, int, error) { 65 | return RevokeOauth(c, google.ProviderId) 66 | } 67 | -------------------------------------------------------------------------------- /service/oauthService/kakao.go: -------------------------------------------------------------------------------- 1 | package oauthService 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | 8 | "github.com/dorajistyle/goyangi/util/log" 9 | "github.com/dorajistyle/goyangi/util/modelHelper" 10 | "github.com/dorajistyle/goyangi/util/oauth2" 11 | "github.com/dorajistyle/goyangi/util/oauth2/kakao" 12 | "github.com/gin-gonic/gin" 13 | "github.com/gin-gonic/gin/binding" 14 | ) 15 | 16 | // KakaoURL return kakao auth url. 17 | func KakaoURL() (string, int) { 18 | return oauth2.OauthURL(kakao.Config), http.StatusOK 19 | } 20 | 21 | // SetKakaoUser set kakao user. 22 | func SetKakaoUser(response *http.Response) (*KakaoUser, error) { 23 | kakaoUser := &KakaoUser{} 24 | defer response.Body.Close() 25 | 26 | log.Debugf("response.Body: %v\n", response.Body) 27 | body, err := ioutil.ReadAll(response.Body) 28 | if err != nil { 29 | return kakaoUser, err 30 | } 31 | unmarshalErr := json.Unmarshal(body, &kakaoUser) 32 | if unmarshalErr != nil { 33 | return kakaoUser, unmarshalErr 34 | } 35 | log.Debugf("\nkakaoUser: %v\n", kakaoUser) 36 | return kakaoUser, err 37 | } 38 | 39 | // OauthKakao link connection and user. 40 | func OauthKakao(c *gin.Context) (int, error) { 41 | var authResponse oauth2.AuthResponse 42 | var oauthUser OauthUser 43 | bindErr := c.MustBindWith(&authResponse, binding.Form) 44 | log.Debugf("bind error : %s\n", bindErr) 45 | log.Debugf("oauthRedirect form: %v", authResponse) 46 | response, token, err := oauth2.OauthRequest(kakao.RequestURL, kakao.Config, authResponse) 47 | if err != nil { 48 | return http.StatusInternalServerError, err 49 | } 50 | kakaoUser, err := SetKakaoUser(response) 51 | if err != nil { 52 | return http.StatusInternalServerError, err 53 | } 54 | modelHelper.AssignValue(&oauthUser, kakaoUser) 55 | log.Debugf("\noauthUser item : %v", oauthUser) 56 | log.Debugf("\noauthUser token : %v", token) 57 | status, err := LoginOrCreateOauthUser(c, &oauthUser, kakao.ProviderId, token) 58 | if err != nil { 59 | return status, err 60 | } 61 | return http.StatusSeeOther, nil 62 | } 63 | 64 | // RevokeKakao revokes kakao oauth connection. 65 | func RevokeKakao(c *gin.Context) (map[string]bool, int, error) { 66 | return RevokeOauth(c, kakao.ProviderId) 67 | } 68 | -------------------------------------------------------------------------------- /service/oauthService/linkedin.go: -------------------------------------------------------------------------------- 1 | package oauthService 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | 8 | "github.com/dorajistyle/goyangi/util/log" 9 | "github.com/dorajistyle/goyangi/util/modelHelper" 10 | "github.com/dorajistyle/goyangi/util/oauth2" 11 | "github.com/dorajistyle/goyangi/util/oauth2/linkedin" 12 | "github.com/gin-gonic/gin" 13 | "github.com/gin-gonic/gin/binding" 14 | ) 15 | 16 | // LinkedinUser is a struct that contained linkedin user information. 17 | type LinkedinUser struct { 18 | ID string `json:"id"` 19 | Email string `json:"emailAddress"` 20 | Username string `json:"firstName"` 21 | Name string `json:"lastName""` 22 | ImageUrl string `json:"pictureUrl"` 23 | ProfileUrl string `json:"publicProfileUrl"` 24 | } 25 | 26 | // LinkedinURL return linkedin auth url. 27 | func LinkedinURL() (string, int) { 28 | return oauth2.OauthURL(linkedin.Config), http.StatusOK 29 | } 30 | 31 | // SetLinkedinUser set linkedin user. 32 | func SetLinkedinUser(response *http.Response) (*LinkedinUser, error) { 33 | linkedinUser := &LinkedinUser{} 34 | defer response.Body.Close() 35 | body, err := ioutil.ReadAll(response.Body) 36 | if err != nil { 37 | return linkedinUser, err 38 | } 39 | unmarshalErr := json.Unmarshal(body, &linkedinUser) 40 | if unmarshalErr != nil { 41 | return linkedinUser, unmarshalErr 42 | } 43 | log.Debugf("\nlinkedinUser: %v\n", linkedinUser) 44 | return linkedinUser, err 45 | } 46 | 47 | // OauthLinkedin link connection and user. 48 | func OauthLinkedin(c *gin.Context) (int, error) { 49 | var authResponse oauth2.AuthResponse 50 | var oauthUser OauthUser 51 | bindErr := c.MustBindWith(&authResponse, binding.Form) 52 | log.Debugf("bind error : %s\n", bindErr) 53 | log.Debugf("oauthRedirect form: %v", authResponse) 54 | response, token, err := oauth2.OauthRequest(linkedin.RequestURL, linkedin.Config, authResponse) 55 | if err != nil { 56 | return http.StatusInternalServerError, err 57 | } 58 | linkedinUser, err := SetLinkedinUser(response) 59 | if err != nil { 60 | return http.StatusInternalServerError, err 61 | } 62 | modelHelper.AssignValue(&oauthUser, linkedinUser) 63 | log.Debugf("\noauthUser item : %v", oauthUser) 64 | log.Debugf("\nlinkedinUser id : %s", linkedinUser.ID) 65 | log.Debugf("\noauthUser id : %s", oauthUser.ID) 66 | status, err := LoginOrCreateOauthUser(c, &oauthUser, linkedin.ProviderId, token) 67 | if err != nil { 68 | return status, err 69 | } 70 | return http.StatusSeeOther, nil 71 | } 72 | 73 | // RevokeLinkedin revokes linkedin oauth connection. 74 | func RevokeLinkedin(c *gin.Context) (map[string]bool, int, error) { 75 | return RevokeOauth(c, linkedin.ProviderId) 76 | } 77 | -------------------------------------------------------------------------------- /service/oauthService/model.go: -------------------------------------------------------------------------------- 1 | package oauthService 2 | 3 | // FacebookUser is a struct that contained facebook user information. 4 | type FacebookUser struct { 5 | ID string `json:"id"` 6 | Username string `json:"name"` 7 | ProfileUrl string `json:"link"` 8 | } 9 | 10 | // GithubUser is a struct that contained github user information. 11 | type GithubUser struct { 12 | UserID int `json:"id"` 13 | Email string `json:"email"` 14 | Username string `json:"login"` 15 | Name string `json:"name""` 16 | ImageUrl string `json:"avatar_url"` 17 | ProfileUrl string `json:"html_url"` 18 | } 19 | 20 | // Email is a struct that contained googleUser's email. 21 | type Email struct { 22 | Value string `json:"value" binding:"required"` 23 | Type string `json:"type" binding:"required"` 24 | } 25 | 26 | // Image is a struct that contained googleUser's image meta data. 27 | type Image struct { 28 | URL string `json:"url" binding:"required"` 29 | IsDefault string `json:"isDefault" binding:"required"` 30 | } 31 | 32 | // GoogleUser is a struct that contained google user information. 33 | type GoogleUser struct { 34 | ID string `json:"id"` 35 | Username string `json:"nickname"` 36 | Emails []Email `json:"emails"` 37 | Name string `json:"displayName"` 38 | Image Image `json:"image"` 39 | ProfileUrl string `json:"url"` 40 | } 41 | 42 | // KakaoUser is a struct that contained kakao user information. 43 | type KakaoUser struct { 44 | ID string `json:"id"` 45 | Email string `json:"email"` 46 | Username string `json:"login"` 47 | Name string `json:"name""` 48 | ImageUrl string `json:"avatar_url"` 49 | ProfileUrl string `json:"html_url"` 50 | } 51 | 52 | // NaverUser is a struct that contained naver user information. 53 | type NaverUser struct { 54 | ID string `json:"id"` 55 | Email string `json:"email"` 56 | Username string `json:"login"` 57 | Name string `json:"name""` 58 | ImageUrl string `json:"avatar_url"` 59 | ProfileUrl string `json:"html_url"` 60 | } 61 | 62 | // TwitterUser is a struct that contained twitter user information. 63 | type TwitterUser struct { 64 | ID string `json:"id"` 65 | Email string `json:"email"` 66 | Name string `json:"name"` 67 | Username string `json:"screen_name"` 68 | ProfileUrl string `json:"url"` 69 | ImageUrl string `json:"profile_image_url"` 70 | } 71 | 72 | // YahooUser is a struct that contained yahoo user information. 73 | type YahooUser struct { 74 | ID string `json:"guid"` 75 | Email string `json:"email"` 76 | Name string `json:"nickname"` 77 | Username string `json:"nickname"` 78 | ImageUrl string `json:"imageURL"` 79 | ProfileUrl string `json:"profileURL"` 80 | } -------------------------------------------------------------------------------- /service/oauthService/naver.go: -------------------------------------------------------------------------------- 1 | package oauthService 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | 8 | "github.com/dorajistyle/goyangi/util/log" 9 | "github.com/dorajistyle/goyangi/util/modelHelper" 10 | "github.com/dorajistyle/goyangi/util/oauth2" 11 | "github.com/dorajistyle/goyangi/util/oauth2/naver" 12 | "github.com/gin-gonic/gin" 13 | "github.com/gin-gonic/gin/binding" 14 | ) 15 | 16 | // NaverURL return naver auth url. 17 | func NaverURL() (string, int) { 18 | return oauth2.OauthURL(naver.Config), http.StatusOK 19 | } 20 | 21 | // SetNaverUser set naver user. 22 | func SetNaverUser(response *http.Response) (*NaverUser, error) { 23 | naverUser := &NaverUser{} 24 | defer response.Body.Close() 25 | log.Debugf("response.Body: %v\n", response.Body) 26 | body, err := ioutil.ReadAll(response.Body) 27 | if err != nil { 28 | return naverUser, err 29 | } 30 | unmarshalErr := json.Unmarshal(body, &naverUser) 31 | if unmarshalErr != nil { 32 | return naverUser, unmarshalErr 33 | } 34 | log.Debugf("\nnaverUser: %v\n", naverUser) 35 | return naverUser, err 36 | } 37 | 38 | // OauthNaver link connection and user. 39 | func OauthNaver(c *gin.Context) (int, error) { 40 | var authResponse oauth2.AuthResponse 41 | var oauthUser OauthUser 42 | bindErr := c.MustBindWith(&authResponse, binding.Form) 43 | log.Debugf("bind error : %s\n", bindErr) 44 | log.Debugf("oauthRedirect form: %v", authResponse) 45 | response, token, err := oauth2.OauthRequest(naver.RequestURL, naver.Config, authResponse) 46 | if err != nil { 47 | return http.StatusInternalServerError, err 48 | } 49 | naverUser, err := SetNaverUser(response) 50 | if err != nil { 51 | return http.StatusInternalServerError, err 52 | } 53 | modelHelper.AssignValue(&oauthUser, naverUser) 54 | log.Debugf("\noauthUser item : %v", oauthUser) 55 | log.Debugf("\noauthUser token : %v", token) 56 | status, err := LoginOrCreateOauthUser(c, &oauthUser, naver.ProviderId, token) 57 | if err != nil { 58 | return status, err 59 | } 60 | return http.StatusSeeOther, nil 61 | } 62 | 63 | // RevokeNaver revokes naver oauth connection. 64 | func RevokeNaver(c *gin.Context) (map[string]bool, int, error) { 65 | return RevokeOauth(c, naver.ProviderId) 66 | } 67 | -------------------------------------------------------------------------------- /service/oauthService/twitter.go: -------------------------------------------------------------------------------- 1 | package oauthService 2 | 3 | import ( 4 | // "encoding/json" 5 | "encoding/json" 6 | "io/ioutil" 7 | "net/http" 8 | 9 | "github.com/dorajistyle/goyangi/util/log" 10 | "github.com/dorajistyle/goyangi/util/modelHelper" 11 | "github.com/dorajistyle/goyangi/util/oauth2" 12 | "github.com/dorajistyle/goyangi/util/oauth2/twitter" 13 | "github.com/gin-gonic/gin" 14 | "github.com/gin-gonic/gin/binding" 15 | ) 16 | 17 | // TwitterURL return twitter auth url. 18 | func TwitterURL() (string, int) { 19 | return oauth2.OauthURL(twitter.Config), http.StatusOK 20 | } 21 | 22 | // SetTwitterUser set twitter user. 23 | func SetTwitterUser(response *http.Response) (*TwitterUser, error) { 24 | twitterUser := &TwitterUser{} 25 | defer response.Body.Close() 26 | body, err := ioutil.ReadAll(response.Body) 27 | if err != nil { 28 | return twitterUser, err 29 | } 30 | unmarshalErr := json.Unmarshal(body, &twitterUser) 31 | if unmarshalErr != nil { 32 | return twitterUser, unmarshalErr 33 | } 34 | log.Debugf("\ntwitterUser: %v\n", twitterUser) 35 | return twitterUser, err 36 | } 37 | 38 | // OauthTwitter link connection and user. 39 | func OauthTwitter(c *gin.Context) (int, error) { 40 | var authResponse oauth2.AuthResponse 41 | var oauthUser OauthUser 42 | bindErr := c.MustBindWith(&authResponse, binding.Form) 43 | log.Debugf("bind error : %s\n", bindErr) 44 | log.Debugf("oauthRedirect form: %v", authResponse) 45 | response, token, err := oauth2.OauthRequest(twitter.RequestURL, twitter.Config, authResponse) 46 | if err != nil { 47 | return http.StatusInternalServerError, err 48 | } 49 | twitterUser, err := SetTwitterUser(response) 50 | if err != nil { 51 | return http.StatusInternalServerError, err 52 | } 53 | modelHelper.AssignValue(&oauthUser, twitterUser) 54 | log.Debugf("\noauthUser item : %v", oauthUser) 55 | status, err := LoginOrCreateOauthUser(c, &oauthUser, twitter.ProviderId, token) 56 | if err != nil { 57 | return status, err 58 | } 59 | return http.StatusSeeOther, nil 60 | } 61 | 62 | // RevokeTwitter revokes twitter oauth connection. 63 | func RevokeTwitter(c *gin.Context) (map[string]bool, int, error) { 64 | return RevokeOauth(c, twitter.ProviderId) 65 | } 66 | -------------------------------------------------------------------------------- /service/oauthService/yahoo.go: -------------------------------------------------------------------------------- 1 | package oauthService 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | 8 | "github.com/dorajistyle/goyangi/util/log" 9 | "github.com/dorajistyle/goyangi/util/modelHelper" 10 | "github.com/dorajistyle/goyangi/util/oauth2" 11 | "github.com/dorajistyle/goyangi/util/oauth2/yahoo" 12 | "github.com/gin-gonic/gin" 13 | "github.com/gin-gonic/gin/binding" 14 | ) 15 | 16 | // YahooURL return yahoo auth url. 17 | func YahooURL() (string, int) { 18 | return oauth2.OauthURL(yahoo.Config), http.StatusOK 19 | } 20 | 21 | // SetYahooUser set yahoo user. 22 | func SetYahooUser(response *http.Response) (*YahooUser, error) { 23 | yahooUser := &YahooUser{} 24 | defer response.Body.Close() 25 | body, err := ioutil.ReadAll(response.Body) 26 | if err != nil { 27 | return yahooUser, err 28 | } 29 | unmarshalErr := json.Unmarshal(body, &yahooUser) 30 | if unmarshalErr != nil { 31 | return yahooUser, unmarshalErr 32 | } 33 | log.Debugf("\nyahooUser: %v\n", yahooUser) 34 | return yahooUser, err 35 | } 36 | 37 | // OauthYahoo link connection and user. 38 | func OauthYahoo(c *gin.Context) (int, error) { 39 | var authResponse oauth2.AuthResponse 40 | var oauthUser OauthUser 41 | bindErr := c.MustBindWith(&authResponse, binding.Form) 42 | log.Debugf("bind error : %s\n", bindErr) 43 | log.Debugf("oauthRedirect form: %v", authResponse) 44 | response, token, err := oauth2.OauthRequest(yahoo.RequestURL, yahoo.Config, authResponse) 45 | if err != nil { 46 | return http.StatusInternalServerError, err 47 | } 48 | yahooUser, err := SetYahooUser(response) 49 | if err != nil { 50 | return http.StatusInternalServerError, err 51 | } 52 | modelHelper.AssignValue(&oauthUser, yahooUser) 53 | log.Debugf("\noauthUser item : %v", oauthUser) 54 | status, err := LoginOrCreateOauthUser(c, &oauthUser, yahoo.ProviderId, token) 55 | if err != nil { 56 | return status, err 57 | } 58 | return http.StatusSeeOther, nil 59 | } 60 | 61 | // RevokeYahoo revokes yahoo oauth connection. 62 | func RevokeYahoo(c *gin.Context) (map[string]bool, int, error) { 63 | return RevokeOauth(c, yahoo.ProviderId) 64 | } 65 | -------------------------------------------------------------------------------- /service/roleService/form.go: -------------------------------------------------------------------------------- 1 | package roleService 2 | 3 | // RoleForm is used when creating or updating a role. 4 | type RoleForm struct { 5 | Name string `form:"name" binding:"required"` 6 | Description string `form:"description" binding:"required"` 7 | } 8 | -------------------------------------------------------------------------------- /service/roleService/role.go: -------------------------------------------------------------------------------- 1 | package roleService 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | 7 | "github.com/dorajistyle/goyangi/db" 8 | "github.com/dorajistyle/goyangi/model" 9 | "github.com/dorajistyle/goyangi/util/log" 10 | "github.com/gin-gonic/gin" 11 | "github.com/gin-gonic/gin/binding" 12 | ) 13 | 14 | // CreateRole creates a role. 15 | func CreateRole(c *gin.Context) (model.Role, int, error) { 16 | var form RoleForm 17 | bindErr := c.MustBindWith(&form, binding.Form) 18 | log.Debugf("bind error : %s\n", bindErr) 19 | name := form.Name 20 | description := form.Description 21 | role := model.Role{Name: name, Description: description} 22 | if db.ORM.Create(&role).Error != nil { 23 | return role, http.StatusInternalServerError, errors.New("Role is not created.") 24 | } 25 | return role, http.StatusCreated, nil 26 | } 27 | 28 | // RetrieveRole retrieves a role. 29 | func RetrieveRole(c *gin.Context) (model.Role, int, error) { 30 | var role model.Role 31 | id := c.Params.ByName("id") 32 | if db.ORM.First(&role, id).RecordNotFound() { 33 | return role, http.StatusNotFound, errors.New("Role is not found.") 34 | } 35 | return role, http.StatusOK, nil 36 | } 37 | 38 | // RetrieveRoles retrieves roles. 39 | func RetrieveRoles(c *gin.Context) []model.Role { 40 | var roles []model.Role 41 | db.ORM.Find(&roles) 42 | return roles 43 | } 44 | 45 | // UpdateRole updates a role. 46 | func UpdateRole(c *gin.Context) (model.Role, int, error) { 47 | var role model.Role 48 | var form RoleForm 49 | id := c.Params.ByName("id") 50 | bindErr := c.MustBindWith(&form, binding.Form) 51 | log.Debugf("bind error : %s\n", bindErr) 52 | if db.ORM.First(&role, id).RecordNotFound() { 53 | return role, http.StatusNotFound, errors.New("Role is not found.") 54 | } 55 | role.Name = form.Name 56 | role.Description = form.Description 57 | if db.ORM.Save(&role).Error != nil { 58 | return role, http.StatusInternalServerError, errors.New("Role is not updated.") 59 | } 60 | return role, http.StatusOK, nil 61 | } 62 | 63 | // DeleteRole deletes a role. 64 | func DeleteRole(c *gin.Context) (int, error) { 65 | log.Debug("deleteRole performed") 66 | var role model.Role 67 | id := c.Params.ByName("id") 68 | if db.ORM.First(&role, id).RecordNotFound() { 69 | return http.StatusNotFound, errors.New("Role is not found.") 70 | } 71 | if db.ORM.Delete(&role).Delete(role).Error != nil { 72 | return http.StatusInternalServerError, errors.New("Role is not deleted.") 73 | } 74 | return http.StatusOK, nil 75 | } 76 | -------------------------------------------------------------------------------- /service/uploadService/form.go: -------------------------------------------------------------------------------- 1 | package uploadService 2 | 3 | // FileForm is used when creating or updating a file. 4 | type FileForm struct { 5 | Id uint `form:"id"` 6 | UserId uint `form:"userId"` 7 | Name string `form:"name" binding:"required"` 8 | Size int `form:"size" binding:"required"` 9 | } 10 | 11 | // FilesForm is used when creating or updating multiple files. 12 | type FilesForm struct { 13 | Files []FileForm `form:"json" binding:"required"` 14 | } 15 | -------------------------------------------------------------------------------- /service/uploadService/image.go: -------------------------------------------------------------------------------- 1 | package uploadService 2 | 3 | import ( 4 | "io" 5 | "mime/multipart" 6 | "net/http" 7 | "strconv" 8 | "sync/atomic" 9 | 10 | "github.com/dorajistyle/goyangi/service/userService" 11 | "github.com/gin-gonic/gin" 12 | "github.com/spf13/viper" 13 | 14 | "github.com/dorajistyle/goyangi/model" 15 | "github.com/dorajistyle/goyangi/util/concurrency" 16 | "github.com/dorajistyle/goyangi/util/log" 17 | "github.com/dorajistyle/goyangi/util/upload" 18 | ) 19 | 20 | var ( 21 | 업로더 = imageUploader() 22 | ImageUploader = imageUploader() 23 | Cargador = imageUploader() 24 | Shàngchuán = imageUploader() 25 | загрузчик = imageUploader() 26 | s3UploadPath string = "" 27 | user model.User 28 | ) 29 | 30 | // imageUploader is a uploader that uploading files. 31 | func imageUploader() concurrency.ConcurrencyManager { 32 | return func(request *http.Request) concurrency.Result { 33 | atomic.AddInt32(concurrency.BusyWorker, 1) 34 | var result concurrency.Result 35 | result.Code = http.StatusOK 36 | var reader *multipart.Reader 37 | var err error 38 | reader, err = request.MultipartReader() 39 | log.Debug("File upload start.") 40 | if err != nil { 41 | log.CheckErrorWithMessage(err, "Uploading failed.") 42 | result.Code = http.StatusInternalServerError 43 | result.Error = err 44 | return result 45 | } 46 | for { 47 | part, err := reader.NextPart() 48 | 49 | uploadedNow := atomic.AddUint32(concurrency.Done, 1) 50 | log.Debugf("count %d", uploadedNow) 51 | if err == io.EOF { 52 | log.Debug("End of file.") 53 | break 54 | } 55 | if part.FileName() == "" { 56 | log.Debug("File name is empty.") 57 | continue 58 | } 59 | err = upload.UploadImageFile(viper.GetString("upload.target"), viper.GetString("app.environment"), s3UploadPath, part) 60 | if err != nil { 61 | log.Error("Image uploading failed. : ", err) 62 | result.Code = http.StatusBadRequest 63 | result.Error = err 64 | return result 65 | } 66 | log.Debug("File uploaded.") 67 | } 68 | log.Debug("Iteration concurrency.Done.") 69 | return result 70 | } 71 | } 72 | 73 | // UploadImages uploads images to a storage. 74 | func UploadImages(c *gin.Context) (int, error) { 75 | r := c.Request 76 | // reader, err := r.MultipartReader() 77 | user, _ = userService.CurrentUser(c) 78 | s3UploadPath = viper.GetString("upload.path.S3Image") + strconv.FormatInt(int64(user.Id), 10) + "/" 79 | // if err != nil { 80 | // return http.StatusInternalServerError, err 81 | // } 82 | return concurrency.Concurrent(r, concurrency.ConcurrencyAgent(r, 업로더, ImageUploader, Cargador, Shàngchuán, загрузчик)) 83 | } 84 | -------------------------------------------------------------------------------- /service/userService/authentication.go: -------------------------------------------------------------------------------- 1 | package userService 2 | 3 | import ( 4 | "github.com/dorajistyle/goyangi/util/log" 5 | "github.com/gin-gonic/gin" 6 | "github.com/gin-gonic/gin/binding" 7 | ) 8 | 9 | // CreateUserAuthentication creates user authentication. 10 | func CreateUserAuthentication(c *gin.Context) (int, error) { 11 | var form LoginForm 12 | bindErr := c.MustBindWith(&form, binding.Form) 13 | log.Debugf("bind error : %s\n", bindErr) 14 | email := form.Email 15 | pass := form.Password 16 | status, err := SetCookieHandler(c, email, pass) 17 | return status, err 18 | } 19 | -------------------------------------------------------------------------------- /service/userService/form.go: -------------------------------------------------------------------------------- 1 | package userService 2 | 3 | // RegistrationForm is used when creating a user. 4 | type RegistrationForm struct { 5 | Username string `form:"registrationUsername" binding:"required"` 6 | Email string `form:"registrationEmail" binding:"required"` 7 | Password string `form:"registrationPassword" binding:"required"` 8 | } 9 | 10 | // RegistrationForm is used when creating a user authentication. 11 | type LoginForm struct { 12 | Email string `form:"loginEmail" binding:"required"` 13 | Password string `form:"loginPassword" binding:"required"` 14 | } 15 | 16 | // UserForm is used when updating a user. 17 | type UserForm struct { 18 | Age uint `form:"age"` 19 | Name string `form:"name" binding:"required"` 20 | } 21 | 22 | // PasswordForm is used when updating a user password. 23 | type PasswordForm struct { 24 | CurrentPassword string `form:"currentPassword" binding:"required"` 25 | Password string `form:"newPassword" binding:"required"` 26 | } 27 | 28 | // SendPasswordResetForm is used when sending a password reset token. 29 | type SendPasswordResetForm struct { 30 | Email string `form:"email" binding:"required"` 31 | } 32 | 33 | // PasswordResetForm is used when reseting a password. 34 | type PasswordResetForm struct { 35 | PasswordResetToken string `form:"token" binding:"required"` 36 | Password string `form:"newPassword" binding:"required"` 37 | } 38 | 39 | // VerifyEmailForm is used when verifying an email. 40 | type VerifyEmailForm struct { 41 | ActivationToken string `form:"token" binding:"required"` 42 | } 43 | 44 | // ActivateForm is used when activating user. 45 | type ActivateForm struct { 46 | Activation bool `form:"activation" binding:"required"` 47 | } 48 | 49 | // UserRoleForm is used when adding or removing a role from a user. 50 | type UserRoleForm struct { 51 | UserId int `form:"userId" binding:"required"` 52 | RoleId int `form:"roleId" binding:"required"` 53 | } 54 | -------------------------------------------------------------------------------- /service/userService/locale/en-us.all.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "link", 4 | "translation": "link" 5 | }, 6 | { 7 | "id": "verify_email_title", 8 | "translation": "Verify your email address for Goyangi." 9 | }, 10 | { 11 | "id": "verify_email_content", 12 | "translation": "Please click below link to verify your email." 13 | }, 14 | { 15 | "id": "reset_password_title", 16 | "translation": "Reset your password for goyangi." 17 | }, 18 | { 19 | "id": "reset_password_content", 20 | "translation": "Please click below link to reset your password." 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /service/userService/locale/ko-kr.all.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "link", 4 | "translation": "연결" 5 | }, 6 | { 7 | "id": "verify_email_title", 8 | "translation": "고양이 서비스 이메일을 검증해주세요." 9 | }, 10 | { 11 | "id": "verify_email_content", 12 | "translation": "이메일을 검증하시려면 아래 링크를 클릭하세요." 13 | }, 14 | { 15 | "id": "reset_password_title", 16 | "translation": "고양이 서비스 암호 초기화 메일입니다." 17 | }, 18 | { 19 | "id": "reset_password_content", 20 | "translation": "암호를 변경하시려면 아래 링크를 클릭하세요." 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /service/userService/password.go: -------------------------------------------------------------------------------- 1 | package userService 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | 7 | "golang.org/x/crypto/bcrypt" 8 | 9 | "time" 10 | 11 | "github.com/dorajistyle/goyangi/db" 12 | "github.com/dorajistyle/goyangi/model" 13 | "github.com/dorajistyle/goyangi/util/crypto" 14 | "github.com/dorajistyle/goyangi/util/email" 15 | "github.com/dorajistyle/goyangi/util/log" 16 | "github.com/dorajistyle/goyangi/util/modelHelper" 17 | "github.com/dorajistyle/goyangi/util/timeHelper" 18 | "github.com/gin-gonic/gin" 19 | "github.com/gin-gonic/gin/binding" 20 | "github.com/nicksnyder/go-i18n/i18n" 21 | "github.com/spf13/viper" 22 | ) 23 | 24 | // SendEmailPasswordResetToken sends a password reset token via email. 25 | func SendEmailPasswordResetToken(to string, token string, locale string) error { 26 | T, _ := i18n.Tfunc(locale) 27 | err := email.SendEmailFromAdmin(to, 28 | T("reset_password_title"), 29 | T("link")+" : "+viper.GetString("api.hostURL")+"/reset/password/"+token, 30 | T("reset_password_content")+"
"+viper.GetString("api.hostURL")+"/reset/password/"+token+"") 31 | return err 32 | } 33 | 34 | // SendPasswordResetToken sends a password reset token. 35 | func SendPasswordResetToken(c *gin.Context) (int, error) { 36 | var user model.User 37 | var sendPasswordResetForm SendPasswordResetForm 38 | var err error 39 | log.Debugf("c.Params : %v", c.Params) 40 | bindErr := c.MustBindWith(&sendPasswordResetForm, binding.Form) 41 | log.Debugf("bind error : %s\n", bindErr) 42 | if db.ORM.Where(&model.User{Email: sendPasswordResetForm.Email}).First(&user).RecordNotFound() { 43 | return http.StatusNotFound, errors.New("User is not found. Please Check the email.") 44 | } 45 | user.PasswordResetUntil = timeHelper.TwentyFourHoursLater() 46 | user.PasswordResetToken, err = crypto.GenerateRandomToken16() 47 | if err != nil { 48 | return http.StatusInternalServerError, err 49 | } 50 | log.Debugf("generated token : %s", user.PasswordResetToken) 51 | status, err := UpdateUserCore(&user) 52 | if err != nil { 53 | return status, err 54 | } 55 | err = SendEmailPasswordResetToken(user.Email, user.PasswordResetToken, "en-us") 56 | if err != nil { 57 | return http.StatusInternalServerError, err 58 | } 59 | return http.StatusOK, nil 60 | } 61 | 62 | // ResetPassword resets a password of user. 63 | func ResetPassword(c *gin.Context) (int, error) { 64 | var user model.User 65 | var passwordResetForm PasswordResetForm 66 | bindErr := c.MustBindWith(&passwordResetForm, binding.Form) 67 | log.Debugf("bind error : %s\n", bindErr) 68 | if db.ORM.Where(&model.User{PasswordResetToken: passwordResetForm.PasswordResetToken}).First(&user).RecordNotFound() { 69 | return http.StatusNotFound, errors.New("User is not found.") 70 | } 71 | isExpired := timeHelper.IsExpired(user.PasswordResetUntil) 72 | log.Debugf("passwordResetUntil : %s", user.PasswordResetUntil.UTC()) 73 | log.Debugf("expired : %t", isExpired) 74 | if isExpired { 75 | return http.StatusForbidden, errors.New("token not valid.") 76 | } 77 | newPassword, err := bcrypt.GenerateFromPassword([]byte(passwordResetForm.Password), 10) 78 | if err != nil { 79 | return http.StatusInternalServerError, errors.New("User is not updated. Password not Generated.") 80 | } 81 | passwordResetForm.Password = string(newPassword) 82 | log.Debugf("user password before : %s ", user.Password) 83 | modelHelper.AssignValue(&user, &passwordResetForm) 84 | user.PasswordResetToken = "" 85 | user.PasswordResetUntil = time.Now() 86 | log.Debugf("user password after : %s ", user.Password) 87 | status, err := UpdateUserCore(&user) 88 | if err != nil { 89 | return status, err 90 | } 91 | status, err = SetCookie(c, user.Token) 92 | return status, err 93 | } 94 | -------------------------------------------------------------------------------- /service/userService/userPermission/permission.go: -------------------------------------------------------------------------------- 1 | package userPermission 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | 7 | "github.com/dorajistyle/goyangi/api/response" 8 | "github.com/dorajistyle/goyangi/model" 9 | "github.com/dorajistyle/goyangi/service/userService" 10 | "github.com/dorajistyle/goyangi/util/log" 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | // HasAdmin checks that user has an admin permission. 15 | func HasAdmin(user *model.User) bool { 16 | name := "admin" 17 | for _, role := range user.Roles { 18 | log.Debugf("HasAdmin role.Name : %s", role.Name) 19 | if role.Name == name { 20 | return true 21 | } 22 | } 23 | return false 24 | } 25 | 26 | // CurrentOrAdmin check that user has admin permission or user is the current user. 27 | func CurrentOrAdmin(user *model.User, userId uint) bool { 28 | log.Debugf("user.Id == userId %d %d %s", user.Id, userId, user.Id == userId) 29 | return (HasAdmin(user) || user.Id == userId) 30 | } 31 | 32 | // CurrentUserIdentical check that userId is same as current user's Id. 33 | func CurrentUserIdentical(c *gin.Context, userId uint) (int, error) { 34 | currentUser, err := userService.CurrentUser(c) 35 | if err != nil { 36 | return http.StatusUnauthorized, errors.New("Auth failed.") 37 | } 38 | if currentUser.Id != userId { 39 | return http.StatusForbidden, errors.New("User is not identical.") 40 | } 41 | 42 | return http.StatusOK, nil 43 | } 44 | 45 | // AuthRequired run function when user logged in. 46 | func AuthRequired(f func(c *gin.Context)) gin.HandlerFunc { 47 | return func(c *gin.Context) { 48 | _, err := userService.CurrentUser(c) 49 | if err != nil { 50 | log.Error("Auth failed.", err) 51 | response.KnownErrorJSON(c, http.StatusUnauthorized, "error.loginPlease", errors.New("Auth failed.")) 52 | return 53 | } 54 | f(c) 55 | return 56 | } 57 | } 58 | 59 | // AdminRequired run function when user logged in and user has an admin role. 60 | func AdminRequired(f func(c *gin.Context)) gin.HandlerFunc { 61 | return func(c *gin.Context) { 62 | user, err := userService.CurrentUser(c) 63 | if err == nil { 64 | if HasAdmin(&user) { 65 | f(c) 66 | log.Debug("User has admin role.") 67 | return 68 | } 69 | } 70 | log.Error("Admin role required.", err) 71 | response.KnownErrorJSON(c, http.StatusUnauthorized, "error.adminRequired", errors.New("Admin role required.")) 72 | return 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /service/userService/verification.go: -------------------------------------------------------------------------------- 1 | package userService 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/dorajistyle/goyangi/db" 9 | "github.com/dorajistyle/goyangi/model" 10 | "github.com/dorajistyle/goyangi/util/crypto" 11 | "github.com/dorajistyle/goyangi/util/email" 12 | "github.com/dorajistyle/goyangi/util/log" 13 | "github.com/dorajistyle/goyangi/util/timeHelper" 14 | "github.com/gin-gonic/gin" 15 | "github.com/gin-gonic/gin/binding" 16 | "github.com/nicksnyder/go-i18n/i18n" 17 | "github.com/spf13/viper" 18 | ) 19 | 20 | // SendEmailVerfication sends an email verification token via email. 21 | func SendEmailVerfication(to string, token string, locale string) error { 22 | T, _ := i18n.Tfunc(locale) 23 | err := email.SendEmailFromAdmin(to, 24 | T("verify_email_title"), 25 | T("link")+" : "+viper.GetString("api.hostURL")+"/verify/email/"+token, 26 | T("verify_email_content")+"
"+viper.GetString("api.hostURL")+"/verify/email/"+token+"") 27 | return err 28 | } 29 | 30 | // SendVerificationToUser sends an email verification token to user. 31 | func SendVerificationToUser(user model.User) (int, error) { 32 | var status int 33 | var err error 34 | user.ActivateUntil = timeHelper.TwentyFourHoursLater() 35 | user.ActivationToken, err = crypto.GenerateRandomToken32() 36 | if err != nil { 37 | return http.StatusInternalServerError, err 38 | } 39 | user.Activation = false 40 | log.Debugf("generated token : %s", user.ActivationToken) 41 | status, err = UpdateUserCore(&user) 42 | if err != nil { 43 | return status, err 44 | } 45 | err = SendEmailVerfication(user.Email, user.ActivationToken, "en-us") 46 | if err != nil { 47 | return http.StatusInternalServerError, err 48 | } 49 | return http.StatusOK, err 50 | } 51 | 52 | // SendVerification sends an email verification token. 53 | func SendVerification(c *gin.Context) (int, error) { 54 | 55 | var user model.User 56 | currentUser, err := CurrentUser(c) 57 | if err != nil { 58 | return http.StatusUnauthorized, errors.New("Unauthorized.") 59 | } 60 | if db.ORM.First(&user, currentUser.Id).RecordNotFound() { 61 | return http.StatusNotFound, errors.New("User is not found.") 62 | } 63 | status, err := SendVerificationToUser(user) 64 | return status, err 65 | } 66 | 67 | // EmailVerification verifies an email of user. 68 | func EmailVerification(c *gin.Context) (int, error) { 69 | var user model.User 70 | var verifyEmailForm VerifyEmailForm 71 | bindErr := c.MustBindWith(&verifyEmailForm, binding.Form) 72 | log.Debugf("bind error : %s\n", bindErr) 73 | log.Debugf("verifyEmailForm.ActivationToken : %s", verifyEmailForm.ActivationToken) 74 | if db.ORM.Where(&model.User{ActivationToken: verifyEmailForm.ActivationToken}).First(&user).RecordNotFound() { 75 | return http.StatusNotFound, errors.New("User is not found.") 76 | } 77 | isExpired := timeHelper.IsExpired(user.ActivateUntil) 78 | log.Debugf("passwordResetUntil : %s", user.ActivateUntil.UTC()) 79 | log.Debugf("expired : %t", isExpired) 80 | if isExpired { 81 | return http.StatusForbidden, errors.New("token not valid.") 82 | } 83 | user.ActivationToken = "" 84 | user.ActivateUntil = time.Now() 85 | user.ActivatedAt = time.Now() 86 | user.Activation = true 87 | status, err := UpdateUserCore(&user) 88 | if err != nil { 89 | return status, err 90 | } 91 | status, err = SetCookie(c, user.Token) 92 | return status, err 93 | } 94 | -------------------------------------------------------------------------------- /util/authHelper/app.go: -------------------------------------------------------------------------------- 1 | package authHelper 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | 7 | "github.com/dorajistyle/goyangi/db" 8 | "github.com/dorajistyle/goyangi/model" 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | // GetAuthorizedAppFromContext gets an authorized app from *gin.Context 13 | func GetAuthorizedAppFromContext(c *gin.Context) (model.App, int, error) { 14 | var app model.App 15 | var status int 16 | var err error 17 | _, claims, status, err := AuthenticateServer(c) 18 | if err != nil { 19 | return app, status, err 20 | } 21 | app, status, err = GetAuthorizedApp(claims["ak"], claims["sk"]) 22 | return app, status, err 23 | } 24 | 25 | // CreateAuthorizedApp creates an authorized app 26 | func CreateAuthorizedAppAndUser(appKey string, secretkey string, name string, username string) (model.App, int, error) { 27 | var app model.App 28 | var user model.User 29 | var err error 30 | result := db.ORM.First(&app, "key = ? and token = ?", appKey, secretkey) 31 | if result.RowsAffected == 0 { 32 | app.Key = appKey 33 | app.Token = secretkey 34 | app.Name = name 35 | db.ORM.Create(&app) 36 | user.Name = username 37 | user.AppId = app.Id 38 | db.ORM.Create(&user) 39 | } 40 | return app, http.StatusOK, err 41 | } 42 | 43 | // RemoveAuthorizedApp removes an authorized app 44 | func RemoveAuthorizedApp(appKey string, secretkey string) (int, error) { 45 | result := db.ORM.Where("key = ? and token = ?", appKey, secretkey).Delete(&model.App{}) 46 | return http.StatusOK, result.Error 47 | } 48 | 49 | // GetAuthorizedApp gets an authorized app from *gin.Context 50 | func GetAuthorizedApp(appKey string, secretkey string) (model.App, int, error) { 51 | var app model.App 52 | var status int 53 | var err error 54 | if db.ORM.First(&app, "key = ? and token = ?", appKey, secretkey).RecordNotFound() { 55 | return app, http.StatusNotFound, errors.New("App is not found.") 56 | } 57 | return app, status, err 58 | } 59 | -------------------------------------------------------------------------------- /util/authHelper/authHelper_suite_test.go: -------------------------------------------------------------------------------- 1 | package authHelper_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestAuthHelper(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Authhelper Suite") 13 | } 14 | -------------------------------------------------------------------------------- /util/authHelper/header.go: -------------------------------------------------------------------------------- 1 | package authHelper 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/dorajistyle/goyangi/util/jwt" 9 | "github.com/dorajistyle/goyangi/util/log" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | // GetTokenString extract a token from an authentication header. 14 | func GetTokenString(c *gin.Context) (string, int, error) { 15 | var token string 16 | if c.Request == nil || c.Request.Header == nil { 17 | return token, http.StatusBadRequest, errors.New("Request header is empty.") 18 | } 19 | authorization := c.Request.Header.Get("Authorization") 20 | if len(authorization) == 0 { 21 | return token, http.StatusUnauthorized, errors.New("Authorization header is empty.") 22 | } 23 | tokens := strings.Split(authorization, " ") 24 | if len(tokens) < 2 { 25 | return token, http.StatusUnauthorized, errors.New("Authorization header is not valid.") 26 | } 27 | token = tokens[1] 28 | log.Debugf("token : %s\n", token) 29 | return token, http.StatusOK, nil 30 | } 31 | 32 | // AuthenticateClient authenticate a token that generated from client that is valid. 33 | func AuthenticateClient(c *gin.Context) (string, map[string]string, int, error) { 34 | log.Debug("AuthenticateClient executed") 35 | token, status, err := GetTokenString(c) 36 | if err != nil { 37 | return token, nil, status, err 38 | } 39 | claims, status, err := jwt.ValidateTokenClient(token) 40 | if err != nil { 41 | return token, nil, status, err 42 | } 43 | claimArray := make(map[string]string) 44 | claimArray["ak"] = claims["ak"].(string) 45 | claimArray["sk"] = claims["sk"].(string) 46 | claimArray["un"] = claims["un"].(string) 47 | return token, claimArray, status, err 48 | } 49 | 50 | // AuthenticateServer authenticate a token that generated from API server that is valid. 51 | func AuthenticateServer(c *gin.Context) (string, map[string]string, int, error) { 52 | log.Debug("AuthenticateServer executed") 53 | token, status, err := GetTokenString(c) 54 | if err != nil { 55 | return token, nil, status, err 56 | } 57 | claims, status, err := jwt.ValidateTokenServer(token) 58 | if err != nil { 59 | return token, nil, status, err 60 | } 61 | claimArray := make(map[string]string) 62 | claimArray["ak"] = claims["ak"].(string) 63 | claimArray["sk"] = claims["sk"].(string) 64 | claimArray["un"] = claims["un"].(string) 65 | return token, claimArray, status, err 66 | } 67 | -------------------------------------------------------------------------------- /util/authHelper/user.go: -------------------------------------------------------------------------------- 1 | package authHelper 2 | 3 | import ( 4 | "github.com/dorajistyle/goyangi/model" 5 | "github.com/dorajistyle/goyangi/util/userHelper" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | // GetAuthorizedUserFromContext gets an authorized user from *gin.Context 10 | func GetAuthorizedUserFromContext(c *gin.Context) (model.User, int, error) { 11 | var user model.User 12 | var status int 13 | var err error 14 | _, claims, status, err := AuthenticateServer(c) 15 | if err != nil { 16 | return user, status, err 17 | } 18 | user, status, err = GetAuthorizedUser(claims["ak"], claims["sk"], claims["un"]) 19 | return user, status, err 20 | } 21 | 22 | // GetAuthorizedUser gets an authorized user from *gin.Context 23 | func GetAuthorizedUser(appKey string, secretkey string, userName string) (model.User, int, error) { 24 | var app model.App 25 | var user model.User 26 | var status int 27 | var err error 28 | app, status, err = GetAuthorizedApp(appKey, secretkey) 29 | if err != nil { 30 | return user, status, err 31 | } 32 | appId := int64(app.Id) 33 | user, status, err = userHelper.FindUserByUserName(appId, userName) 34 | return user, status, err 35 | } 36 | -------------------------------------------------------------------------------- /util/aws/aws.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "github.com/goamz/goamz/aws" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | // Auth return the aws authentication. 9 | func Auth() aws.Auth { 10 | return aws.Auth{ 11 | AccessKey: viper.GetString("aws.accessKeyID"), 12 | SecretKey: viper.GetString("aws.secretAccessKey"), 13 | } 14 | } 15 | 16 | // Region return the aws region from string. 17 | func Region(regionName string) aws.Region { 18 | switch regionName { 19 | case "APNortheast": 20 | return aws.APNortheast 21 | case "APSoutheast": 22 | return aws.APSoutheast 23 | case "APSoutheast2": 24 | return aws.APSoutheast2 25 | case "EUCentral": 26 | return aws.EUCentral 27 | case "EUWest": 28 | return aws.EUWest 29 | case "USEast": 30 | return aws.USEast 31 | case "USWest": 32 | return aws.USWest 33 | case "USWest2": 34 | return aws.USWest2 35 | case "USGovWest": 36 | return aws.USGovWest 37 | case "SAEast": 38 | return aws.SAEast 39 | // case "CNNorth": 40 | // return aws.CNNorth 41 | } 42 | return aws.Region{} 43 | } 44 | -------------------------------------------------------------------------------- /util/aws/s3.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/goamz/goamz/aws" 7 | "github.com/goamz/goamz/s3" 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | // Connection create connection of S3. 12 | func Connection(auth aws.Auth, region aws.Region) *s3.S3 { 13 | return s3.New(auth, region) 14 | } 15 | 16 | // Bucket get bucket of S3 via bucket name. 17 | func Bucket(connection *s3.S3, bucketName string) *s3.Bucket { 18 | return connection.Bucket(bucketName) 19 | } 20 | 21 | // List get list from bucket. 22 | func List(bucket *s3.Bucket, prefix, delim, marker string, max int) (*s3.ListResp, error) { 23 | return bucket.List(prefix, delim, marker, max) 24 | } 25 | 26 | // MyTestBucket get bucket from config. 27 | func MyTestBucket() *s3.Bucket { 28 | return Bucket(Connection(Auth(), Region(viper.GetString("aws.s3.region"))), viper.GetString("aws.s3.testBucket.name")) 29 | } 30 | 31 | // MyTestBucketList get list from MyBucket. 32 | func MyTestBucketList(prefix, delim, marker string, max int) (*s3.ListResp, error) { 33 | return List(MyTestBucket(), prefix, delim, marker, max) 34 | } 35 | 36 | // PutToMyTestBucket put a file to a bucket. 37 | func PutToMyTestBucket(prefix string, keyname string, wb *bytes.Buffer, contentType string, aclType string) error { 38 | acl := s3.ACL(aclType) 39 | return MyTestBucket().Put(prefix+keyname, wb.Bytes(), contentType, acl, s3.Options{}) 40 | } 41 | 42 | // PutToMyPrivateTestBucket put a file to the MyBucket. 43 | func PutToMyPrivateTestBucket(subdir string, keyname string, wb *bytes.Buffer, contentType string) error { 44 | return PutToMyTestBucket(viper.GetString("aws.s3.bucket.prefix"), subdir+keyname, wb, contentType, "private") 45 | } 46 | 47 | // PutToMyPublicTestBucket put a file to the MyBucket. 48 | func PutToMyPublicTestBucket(subdir string, keyname string, wb *bytes.Buffer, contentType string) error { 49 | return PutToMyTestBucket(viper.GetString("aws.s3.testBucket.prefix"), subdir+keyname, wb, contentType, "public-read") 50 | } 51 | 52 | // DelFromMyTestBucket delete a file from a bucket. 53 | func DelFromMyTestBucket(prefix string, keyname string) error { 54 | return MyTestBucket().Del(viper.GetString("aws.s3.bucket.prefix") + prefix + keyname) 55 | } 56 | 57 | // MyBucket get bucket from config. 58 | func MyBucket() *s3.Bucket { 59 | return Bucket(Connection(Auth(), Region(viper.GetString("aws.s3.region"))), viper.GetString("aws.s3.bucket.name")) 60 | } 61 | 62 | // MyBucketList get list from MyBucket. 63 | func MyBucketList(prefix, delim, marker string, max int) (*s3.ListResp, error) { 64 | return List(MyBucket(), prefix, delim, marker, max) 65 | } 66 | 67 | // PutToMyBucket put a file to a bucket. 68 | func PutToMyBucket(prefix string, keyname string, wb *bytes.Buffer, contentType string, aclType string) error { 69 | acl := s3.ACL(aclType) 70 | return MyBucket().Put(prefix+keyname, wb.Bytes(), contentType, acl, s3.Options{}) 71 | } 72 | 73 | // PutToMyPrivateBucket put a file to the MyBucket. 74 | func PutToMyPrivateBucket(subdir string, keyname string, wb *bytes.Buffer, contentType string) error { 75 | return PutToMyBucket(viper.GetString("aws.s3.bucket.prefix"), subdir+keyname, wb, contentType, "private") 76 | } 77 | 78 | // PutToMyPublicBucket put a file to the MyBucket. 79 | func PutToMyPublicBucket(subdir string, keyname string, wb *bytes.Buffer, contentType string) error { 80 | return PutToMyBucket(viper.GetString("aws.s3.bucket.prefix"), subdir+keyname, wb, contentType, "public-read") 81 | } 82 | 83 | // DelFromMyBucket delete a file from a bucket. 84 | func DelFromMyBucket(prefix string, keyname string) error { 85 | return MyBucket().Del(viper.GetString("aws.s3.bucket.prefix") + prefix + keyname) 86 | } 87 | -------------------------------------------------------------------------------- /util/concurrency/concurrency.go: -------------------------------------------------------------------------------- 1 | package concurrency 2 | 3 | import ( 4 | "errors" 5 | "mime/multipart" 6 | "net/http" 7 | "sync/atomic" 8 | "time" 9 | 10 | "github.com/dorajistyle/goyangi/util/log" 11 | "github.com/spf13/viper" 12 | ) 13 | 14 | type ConcurrencyStatus bool 15 | type ConcurrencyManagerLegacy func(reader *multipart.Reader) ConcurrencyStatus 16 | 17 | type ConcurrencyManager func(request *http.Request) Result 18 | 19 | var ( 20 | Done *uint32 = new(uint32) 21 | BusyWorker *int32 = new(int32) 22 | ) 23 | 24 | // Result is the struct that contain http status code and error. 25 | type Result struct { 26 | Code int 27 | Error error 28 | } 29 | 30 | // Concurrent. 31 | func Concurrent(request *http.Request, result Result) (int, error) { 32 | c := make(chan Result) 33 | defer close(c) 34 | go func() { 35 | c <- result 36 | }() 37 | 38 | timeout := time.After(viper.GetDuration("upload.timeout") * time.Second) 39 | select { 40 | case res := <-c: 41 | log.Debugf("End of Upload : %v", res) 42 | workingNow := atomic.AddInt32(BusyWorker, -1) 43 | log.Debugf("All files are Done. Working concurrencyer count : %d", workingNow) 44 | if workingNow == 0 { 45 | return res.Code, res.Error 46 | } 47 | case <-timeout: 48 | err := errors.New("Request timed out.") 49 | log.Warnf(err.Error()) 50 | return http.StatusBadRequest, err 51 | } 52 | return http.StatusBadRequest, errors.New("Invalid Request.") 53 | } 54 | 55 | // ConcurrencyAgent is loadbalancer of concurrencier. 56 | func ConcurrencyAgent(request *http.Request, replicas ...ConcurrencyManager) Result { 57 | for { 58 | workingNow := atomic.LoadInt32(BusyWorker) 59 | if len(replicas) > int(workingNow) { 60 | break 61 | } 62 | time.Sleep(time.Second) 63 | // log.Debugf("working concurrencyer count full (BusyWorker/replicas) : (%d/%d) ", workingNow, len(replicas)) 64 | } 65 | c := make(chan Result) 66 | 67 | concurrencyReplica := func(i int) { 68 | c <- replicas[i](request) 69 | } 70 | workingNow := atomic.LoadInt32(BusyWorker) 71 | log.Debugf("workingNow, len(replicas) : %d %d", workingNow, len(replicas)) 72 | 73 | go concurrencyReplica(int(workingNow)) 74 | // go concurrencyReplica(0) 75 | return <-c 76 | } 77 | -------------------------------------------------------------------------------- /util/concurrency/concurrency_suite_test.go: -------------------------------------------------------------------------------- 1 | package concurrency_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestConcurrency(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Concurrency Suite") 13 | } 14 | -------------------------------------------------------------------------------- /util/concurrency/concurrency_test.go: -------------------------------------------------------------------------------- 1 | package concurrency_test 2 | 3 | import ( 4 | "net/http" 5 | // "github.com/gin-gonic/gin" 6 | "bytes" 7 | "io" 8 | "io/ioutil" 9 | "mime/multipart" 10 | "sync/atomic" 11 | 12 | "github.com/dorajistyle/goyangi/util/concurrency" 13 | viper "github.com/dorajistyle/goyangi/util/viper" 14 | . "github.com/onsi/ginkgo" 15 | . "github.com/onsi/gomega" 16 | ) 17 | 18 | func init() { 19 | viper.LoadConfig() 20 | } 21 | 22 | // mockCocurrencytManager 23 | func mockCocurrencytManager() concurrency.ConcurrencyManager { 24 | return func(request *http.Request) concurrency.Result { 25 | atomic.AddInt32(concurrency.BusyWorker, 1) 26 | var result concurrency.Result 27 | result.Code = http.StatusOK 28 | reader, err := request.MultipartReader() 29 | 30 | // user, _ = userService.CurrentUser(c) 31 | 32 | if err != nil { 33 | result.Code = http.StatusInternalServerError 34 | result.Error = err 35 | return result 36 | } 37 | userAgent := request.Header.Get("User-Agent") 38 | var count int 39 | count = 0 40 | for { 41 | count += 1 42 | part, err := reader.NextPart() 43 | 44 | // uploadedNow := atomic.AddUint32(concurrency.Done, 1) 45 | 46 | if err == io.EOF || part == nil { 47 | 48 | break 49 | } 50 | if part.FormName() == "" { 51 | 52 | continue 53 | } 54 | result.Code, result.Error = ConcurrencyRun(part, userAgent) 55 | } 56 | return result 57 | } 58 | } 59 | 60 | func ConcurrencyRun(part *multipart.Part, userAgent string) (int, error) { 61 | var err error 62 | return 200, err 63 | } 64 | 65 | var _ = Describe("concurrency", func() { 66 | var ( 67 | err error 68 | req *http.Request 69 | mockManager1 concurrency.ConcurrencyManager 70 | mockManager2 concurrency.ConcurrencyManager 71 | mockManager3 concurrency.ConcurrencyManager 72 | mockManager4 concurrency.ConcurrencyManager 73 | mockManager5 concurrency.ConcurrencyManager 74 | ) 75 | 76 | BeforeEach(func() { 77 | mockManager1 = mockCocurrencytManager() 78 | mockManager2 = mockCocurrencytManager() 79 | mockManager3 = mockCocurrencytManager() 80 | mockManager4 = mockCocurrencytManager() 81 | mockManager5 = mockCocurrencytManager() 82 | }) 83 | 84 | Describe("Run a concurrent", func() { 85 | 86 | Context("when concurrent finished", func() { 87 | BeforeEach(func() { 88 | 89 | req = &http.Request{ 90 | Method: "POST", 91 | Header: http.Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}}, 92 | Body: ioutil.NopCloser(new(bytes.Buffer)), 93 | } 94 | // multipart, err := req.MultipartReader() 95 | // if multipart == nil { 96 | // t.Error("expected multipart;", err) 97 | // } 98 | 99 | // req.Header = Header{"Content-Type": {"text/plain"}} 100 | // multipart, err = req.MultipartReader() 101 | // if multipart != nil { 102 | // t.Error("unexpected multipart for text/plain") 103 | // } 104 | 105 | _, err = concurrency.Concurrent(req, concurrency.ConcurrencyAgent(req, mockManager1, mockManager2, mockManager3, mockManager4, mockManager5)) 106 | 107 | }) 108 | It("err should be nil.", func() { 109 | Expect(err).To(BeNil()) 110 | }) 111 | 112 | }) 113 | }) 114 | 115 | }) 116 | -------------------------------------------------------------------------------- /util/crypto/crypto_suite_test.go: -------------------------------------------------------------------------------- 1 | package crypto_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestCrypto(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Crypto Suite") 13 | } 14 | -------------------------------------------------------------------------------- /util/crypto/md5.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | func GenerateMD5Hash(email string) string { 10 | email = strings.ToLower(strings.TrimSpace(email)) 11 | hash := md5.New() 12 | hash.Write([]byte(email)) 13 | return fmt.Sprintf("%x", hash.Sum(nil)) 14 | } 15 | -------------------------------------------------------------------------------- /util/crypto/md5_test.go: -------------------------------------------------------------------------------- 1 | package crypto_test 2 | 3 | import ( 4 | . "github.com/dorajistyle/goyangi/util/crypto" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | var _ = Describe("Md5", func() { 11 | var ( 12 | emailOne string 13 | emailTwo string 14 | ) 15 | 16 | BeforeEach(func() { 17 | emailOne = "testOne@test.com" 18 | emailTwo = "testTwo@test.com" 19 | }) 20 | 21 | Describe("Generate MD5 hash from email", func() { 22 | Context("when the MD5 hash of emailOne generated successfully", func() { 23 | It("should be a correct hash", func() { 24 | Expect(GenerateMD5Hash(emailOne)).To(Equal("d45cfb8dfe120832336109537e52d1c7")) 25 | }) 26 | }) 27 | Context("when the MD5 hash of emailTwo generated successfully", func() { 28 | It("should be a correct hash", func() { 29 | Expect(GenerateMD5Hash(emailTwo)).To(Equal("327e27ad95910153a96d255833c45328")) 30 | }) 31 | }) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /util/crypto/token.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "crypto/rand" 5 | "fmt" 6 | ) 7 | 8 | func GenerateRandomToken16() (string, error) { 9 | return GenerateRandomToken(16) 10 | } 11 | 12 | func GenerateRandomToken32() (string, error) { 13 | return GenerateRandomToken(32) 14 | } 15 | 16 | func GenerateRandomToken(n int) (string, error) { 17 | token := make([]byte, n) 18 | _, err := rand.Read(token) 19 | // %x base 16, lower-case, two characters per byte 20 | return fmt.Sprintf("%x", token), err 21 | 22 | } 23 | -------------------------------------------------------------------------------- /util/crypto/token_test.go: -------------------------------------------------------------------------------- 1 | package crypto_test 2 | 3 | import ( 4 | . "github.com/dorajistyle/goyangi/util/crypto" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | var _ = Describe("Token", func() { 11 | var ( 12 | num32 int 13 | num64 int 14 | num128 int 15 | token string 16 | err error 17 | ) 18 | 19 | BeforeEach(func() { 20 | num32 = 32 21 | num64 = 64 22 | num128 = 128 23 | }) 24 | 25 | Describe("Generate random token", func() { 26 | Context("when random token generated successfully", func() { 27 | BeforeEach(func() { 28 | token, err = GenerateRandomToken16() 29 | }) 30 | It("should have length 32", func() { 31 | Expect(len(token)).To(Equal(num32)) 32 | }) 33 | It("should not error", func() { 34 | Expect(err).NotTo(HaveOccurred()) 35 | }) 36 | }) 37 | 38 | Context("when random token generated successfully", func() { 39 | BeforeEach(func() { 40 | token, err = GenerateRandomToken32() 41 | }) 42 | It("should have length 64", func() { 43 | Expect(len(token)).To(Equal(num64)) 44 | }) 45 | It("should not error", func() { 46 | Expect(err).NotTo(HaveOccurred()) 47 | }) 48 | }) 49 | 50 | Context("when random token generated successfully", func() { 51 | BeforeEach(func() { 52 | token, err = GenerateRandomToken(num64) 53 | }) 54 | It("should have length 128", func() { 55 | Expect(len(token)).To(Equal(num128)) 56 | }) 57 | It("should not error", func() { 58 | Expect(err).NotTo(HaveOccurred()) 59 | }) 60 | }) 61 | 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /util/file/file.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | // UploadPath gets upload path. 12 | func UploadPath() (string, error) { 13 | uploadPath := viper.GetString("upload.path.local") 14 | err := os.MkdirAll(uploadPath, 0777) 15 | return uploadPath, err 16 | } 17 | 18 | // SaveLocal save a file locally. 19 | func SaveLocal(fileName string, wb *bytes.Buffer) error { 20 | uploadPath, err := UploadPath() 21 | if err != nil { 22 | return err 23 | } 24 | var out *os.File 25 | out, err = os.Create(uploadPath + fileName) 26 | defer out.Close() 27 | if err != nil { 28 | return err 29 | } 30 | _, err = io.Copy(out, wb) 31 | return err 32 | } 33 | 34 | // DeleteLocal delete a file locally. 35 | func DeleteLocal(fileName string) error { 36 | uploadPath, err := UploadPath() 37 | if err != nil { 38 | return err 39 | } 40 | err = os.Remove(uploadPath + fileName) 41 | return err 42 | } 43 | -------------------------------------------------------------------------------- /util/file/file_suite_test.go: -------------------------------------------------------------------------------- 1 | package file_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestFile(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "File Suite") 13 | } 14 | -------------------------------------------------------------------------------- /util/file/file_test.go: -------------------------------------------------------------------------------- 1 | package file_test 2 | 3 | import ( 4 | "bytes" 5 | 6 | viper "github.com/dorajistyle/goyangi/util/viper" 7 | 8 | . "github.com/dorajistyle/goyangi/util/file" 9 | 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | func init() { 15 | viper.LoadConfig() 16 | } 17 | 18 | var _ = Describe("File", func() { 19 | var ( 20 | filename string 21 | err error 22 | wb *bytes.Buffer 23 | ) 24 | 25 | BeforeEach(func() { 26 | filename = "testfile.txt" 27 | wb = new(bytes.Buffer) 28 | }) 29 | 30 | Describe("Create a directory", func() { 31 | Context("when the uploadPath is created successfully", func() { 32 | BeforeEach(func() { 33 | _, err = UploadPath() 34 | }) 35 | 36 | It("should not error", func() { 37 | Expect(err).NotTo(HaveOccurred()) 38 | }) 39 | }) 40 | }) 41 | Describe("Save a file to local directory", func() { 42 | Context("when the file saved successfully", func() { 43 | BeforeEach(func() { 44 | err = SaveLocal(filename, wb) 45 | }) 46 | It("should not error", func() { 47 | Expect(err).NotTo(HaveOccurred()) 48 | }) 49 | }) 50 | }) 51 | Describe("Delete a file to local directory", func() { 52 | Context("when the file deleted successfully", func() { 53 | BeforeEach(func() { 54 | err = SaveLocal(filename, wb) 55 | err = DeleteLocal(filename) 56 | }) 57 | It("should not error", func() { 58 | Expect(err).NotTo(HaveOccurred()) 59 | }) 60 | }) 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /util/ginHelper/request.go: -------------------------------------------------------------------------------- 1 | package ginHelper 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | // PostFormArray returns a slice of strings for a given form key, plus 8 | // a boolean value whether at least one value exists for the given key. 9 | func PostFormArray(c *gin.Context, key string) ([]string, bool) { 10 | req := c.Request 11 | req.ParseForm() 12 | req.ParseMultipartForm(32 << 20) // 32 MB 13 | if values := req.PostForm[key]; len(values) > 0 { 14 | return values, true 15 | } 16 | if req.MultipartForm != nil && req.MultipartForm.File != nil { 17 | if values := req.MultipartForm.Value[key]; len(values) > 0 { 18 | return values, true 19 | } 20 | } 21 | return []string{}, false 22 | } -------------------------------------------------------------------------------- /util/httpHelper/http.go: -------------------------------------------------------------------------------- 1 | package httpHelper 2 | 3 | import ( 4 | // "bytes" 5 | "crypto/tls" 6 | "io/ioutil" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/dorajistyle/goyangi/util/log" 11 | ) 12 | 13 | // PostToTarget send a post request to a target address. 14 | func PostToTarget(req *http.Request) (string, int, error) { 15 | client := &http.Client{Timeout: time.Second * 10} 16 | return PostToTargeWithClient(client, req) 17 | } 18 | 19 | // PostToTargetSSLSelfSigned send a post request to a target. 20 | func PostToTargetSSLSelfSigned(req *http.Request) (string, int, error) { 21 | tr := &http.Transport{ 22 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 23 | } 24 | client := &http.Client{Transport: tr, 25 | Timeout: time.Second * 10} 26 | return PostToTargeWithClient(client, req) 27 | } 28 | 29 | // PostToTargeWithClient send a post request to a target with custom client. 30 | func PostToTargeWithClient(client *http.Client, req *http.Request) (string, int, error) { 31 | var responseBody string 32 | resp, err := client.Do(req) 33 | if err != nil { 34 | return responseBody, http.StatusBadRequest, err 35 | // panic(err) 36 | } 37 | defer resp.Body.Close() 38 | // 39 | log.Debugf("response Status: %s\n", resp.Status) 40 | log.Debugf("response Headers: %s\n", resp.Header) 41 | body, err := ioutil.ReadAll(resp.Body) 42 | if err != nil { 43 | return responseBody, http.StatusBadRequest, err 44 | // panic(err) 45 | } 46 | responseBody = string(body) 47 | log.Debugf("response Body:\n", responseBody) 48 | return responseBody, http.StatusOK, nil 49 | } 50 | -------------------------------------------------------------------------------- /util/image/image_suite_test.go: -------------------------------------------------------------------------------- 1 | package image_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestImage(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Image Suite") 13 | } 14 | -------------------------------------------------------------------------------- /util/image/testdata/cat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dorajistyle/goyangi/dffa276e98f80ad1c5fd97cb5339c8a844491725/util/image/testdata/cat.gif -------------------------------------------------------------------------------- /util/image/testdata/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dorajistyle/goyangi/dffa276e98f80ad1c5fd97cb5339c8a844491725/util/image/testdata/cat.jpg -------------------------------------------------------------------------------- /util/image/testdata/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dorajistyle/goyangi/dffa276e98f80ad1c5fd97cb5339c8a844491725/util/image/testdata/cat.png -------------------------------------------------------------------------------- /util/interfaceHelper/interfaceHelper.go: -------------------------------------------------------------------------------- 1 | package interfaceHelper 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | ) 7 | 8 | // GetInt64 recognize a type of interface and return it's value 9 | func GetInt64(unknown interface{}) (int64, error) { 10 | switch i := unknown.(type) { 11 | case float32: 12 | return int64(i), nil 13 | case float64: 14 | return int64(i), nil 15 | case int: 16 | return int64(i), nil 17 | case int8: 18 | return int64(i), nil 19 | case int16: 20 | return int64(i), nil 21 | case int32: 22 | return int64(i), nil 23 | case int64: 24 | return i, nil 25 | case uint: 26 | return int64(i), nil 27 | case uint8: 28 | return int64(i), nil 29 | case uint16: 30 | return int64(i), nil 31 | case uint32: 32 | return int64(i), nil 33 | case uint64: 34 | return int64(i), nil 35 | case string: 36 | v, err := strconv.ParseInt(i, 10, 64) 37 | return v, err 38 | // ...other cases... 39 | default: 40 | return -1, errors.New("get int64: unknown value is of incompatible type") 41 | } 42 | } -------------------------------------------------------------------------------- /util/interfaceHelper/interfaceHelper_suite_test.go: -------------------------------------------------------------------------------- 1 | package interfaceHelper_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestInterfaceHelper(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "InterfaceHelper Suite") 13 | } 14 | -------------------------------------------------------------------------------- /util/interfaceHelper/interfaceHelper_test.go: -------------------------------------------------------------------------------- 1 | package interfaceHelper_test 2 | 3 | import ( 4 | . "github.com/dorajistyle/goyangi/util/interfaceHelper" 5 | viper "github.com/dorajistyle/goyangi/util/viper" 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func init() { 11 | viper.LoadConfig() 12 | } 13 | 14 | var _ = Describe("InterfaceHelper", func() { 15 | var ( 16 | // inputInt64 interface{} 17 | valueFloat32 float32 18 | valueFloat64 float64 19 | valueInt int 20 | valueInt8 int8 21 | valueInt16 int16 22 | valueInt32 int32 23 | valueInt64 int64 24 | valueUInt uint 25 | valueUInt8 uint8 26 | valueUInt16 uint16 27 | valueUInt32 uint32 28 | valueUInt64 uint64 29 | valueString string 30 | outputInt64 int64 31 | ) 32 | 33 | BeforeEach(func() { 34 | valueFloat32 = 124.00 35 | valueFloat64 = 124.00 36 | valueInt = 124 37 | valueInt8 = 124 38 | valueInt16 = 124 39 | valueInt32 = 124 40 | valueInt64 = 124 41 | valueUInt = 124 42 | valueUInt8 = 124 43 | valueUInt16 = 124 44 | valueUInt32 = 124 45 | valueUInt64 = 124 46 | valueString = "124" 47 | }) 48 | 49 | Describe("Get int64 value from interface{}", func() { 50 | 51 | Context("when getting the int64 value successfully", func() { 52 | BeforeEach(func() { 53 | 54 | }) 55 | It("that casted from float32 type should be valueInt64.", func() { 56 | outputInt64, _ = GetInt64(valueFloat32) 57 | Expect(outputInt64).To(Equal(valueInt64)) 58 | }) 59 | It("that casted from float64 type should be valueInt64.", func() { 60 | outputInt64, _ = GetInt64(valueFloat64) 61 | Expect(outputInt64).To(Equal(valueInt64)) 62 | }) 63 | It("that casted from int type should be valueInt64.", func() { 64 | outputInt64, _ = GetInt64(valueInt) 65 | Expect(outputInt64).To(Equal(valueInt64)) 66 | }) 67 | It("that casted from int8 type should be valueInt64.", func() { 68 | outputInt64, _ = GetInt64(valueInt8) 69 | Expect(outputInt64).To(Equal(valueInt64)) 70 | }) 71 | It("that casted from int16 type should be valueInt64.", func() { 72 | outputInt64, _ = GetInt64(valueInt16) 73 | Expect(outputInt64).To(Equal(valueInt64)) 74 | }) 75 | It("that casted from int32 type should be valueInt64.", func() { 76 | outputInt64, _ = GetInt64(valueInt32) 77 | Expect(outputInt64).To(Equal(valueInt64)) 78 | }) 79 | It("that casted from int64 type should be valueInt64.", func() { 80 | outputInt64, _ = GetInt64(valueInt64) 81 | Expect(outputInt64).To(Equal(valueInt64)) 82 | }) 83 | It("that casted from uint type should be valueInt64.", func() { 84 | outputInt64, _ = GetInt64(valueUInt) 85 | Expect(outputInt64).To(Equal(valueInt64)) 86 | }) 87 | It("that casted from uint8 type should be valueInt64.", func() { 88 | outputInt64, _ = GetInt64(valueUInt8) 89 | Expect(outputInt64).To(Equal(valueInt64)) 90 | }) 91 | It("that casted from uint16 type should be valueInt64.", func() { 92 | outputInt64, _ = GetInt64(valueUInt16) 93 | Expect(outputInt64).To(Equal(valueInt64)) 94 | }) 95 | It("that casted from uint32 type should be valueInt64.", func() { 96 | outputInt64, _ = GetInt64(valueUInt32) 97 | Expect(outputInt64).To(Equal(valueInt64)) 98 | }) 99 | It("that casted from uint64 type should be valueInt64.", func() { 100 | outputInt64, _ = GetInt64(valueUInt64) 101 | Expect(outputInt64).To(Equal(valueInt64)) 102 | }) 103 | It("that casted from string type should be valueInt64.", func() { 104 | outputInt64, _ = GetInt64(valueString) 105 | Expect(outputInt64).To(Equal(valueInt64)) 106 | }) 107 | 108 | }) 109 | }) 110 | }) 111 | -------------------------------------------------------------------------------- /util/jwt/jwt_suite_test.go: -------------------------------------------------------------------------------- 1 | package jwt_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestJwt(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Jwt Suite") 13 | } 14 | -------------------------------------------------------------------------------- /util/log/error.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "runtime" 5 | 6 | "golang.org/x/exp/slog" 7 | ) 8 | 9 | // CheckError check error and return true if error is nil and return false if error is not nil. 10 | func CheckError(err error) bool { 11 | return CheckErrorWithMessage(err, "") 12 | } 13 | 14 | // CheckError check error and return true if error is nil and return false if error is not nil. 15 | func CheckErrorNoStack(err error) bool { 16 | return CheckErrorNoStackWithMessage(err, "") 17 | } 18 | 19 | // CheckErrorWithMessage check error with message and log messages with stack. And then return true if error is nil and return false if error is not nil. 20 | func CheckErrorWithMessage(err error, msg string, args ...interface{}) bool { 21 | if err != nil { 22 | var stack [4096]byte 23 | runtime.Stack(stack[:], false) 24 | // slog.Errorf(msg, args) 25 | if len(args) == 0 { 26 | // slog.Error(msg + fmt.Sprintf("%q\n%s\n", err, stack[:])) #logrus Legacy 27 | slog.Error(msg, err, stack) 28 | } else { 29 | // slog.Error(fmt.Sprintf(msg, args...) + fmt.Sprintf("%q\n%s\n", err, stack[:])) #logrus Legacy 30 | slog.Error(msg, err, stack) 31 | } 32 | // slog.Printf(msg+"\n%q\n%s\n",args, err, stack[:]) 33 | return false 34 | } 35 | return true 36 | } 37 | 38 | // CheckErrorNoStackWithMessage check error with message and return true if error is nil and return false if error is not nil. 39 | func CheckErrorNoStackWithMessage(err error, msg string, args ...interface{}) bool { 40 | if err != nil { 41 | if len(args) == 0 { 42 | // slog.Error(msg + fmt.Sprintf("%q\n", err)) #logrus Legacy 43 | slog.Error(msg, err) 44 | } else { 45 | // slog.Error(fmt.Sprintf(msg, args...) + fmt.Sprintf("%q\n", err)) #logrus Legacy 46 | slog.Error(msg, err) 47 | } 48 | return false 49 | } 50 | return true 51 | } 52 | -------------------------------------------------------------------------------- /util/log/logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "time" 7 | 8 | // "github.com/dorajistyle/goyangi/util/octokit" 9 | "github.com/lmittmann/tint" 10 | "github.com/spf13/viper" 11 | "golang.org/x/exp/slog" 12 | lumberjack "gopkg.in/natefinch/lumberjack.v2" 13 | ) 14 | 15 | func LumberJackLogger(filePath string, maxSize int, maxBackups int, maxAge int) *lumberjack.Logger { 16 | return &lumberjack.Logger{ 17 | Filename: filePath, 18 | MaxSize: maxSize, // megabytes 19 | MaxBackups: maxBackups, 20 | MaxAge: maxAge, //days 21 | } 22 | } 23 | 24 | func InitLogToStdoutDebug() { 25 | 26 | // var programLevel = new(slog.LevelVar) 27 | // programLevel.Set() 28 | // h := slog.HandlerOptions{Level: programLevel}.NewTextHandler(os.Stdout) # Legacy without tint 29 | h := tint.NewHandler(os.Stdout, &tint.Options{Level: slog.LevelDebug, TimeFormat: time.Kitchen}) 30 | slog.SetDefault(slog.New(h)) 31 | 32 | } 33 | 34 | func InitLogToStdout() { 35 | h := tint.NewHandler(os.Stdout, &tint.Options{Level: slog.LevelWarn, TimeFormat: time.DateTime}) 36 | slog.SetDefault(slog.New(h)) 37 | } 38 | 39 | func InitLogToFile() { 40 | out := LumberJackLogger(viper.GetString("log.error.filepath"), viper.GetInt("log.error.maxSize"), viper.GetInt("log.error.maxBackups"), viper.GetInt("log.error.maxAge")) 41 | var programLevel = new(slog.LevelVar) 42 | programLevel.Set(slog.LevelWarn) 43 | h := slog.NewJSONHandler(out, &slog.HandlerOptions{Level: programLevel}) 44 | slog.SetDefault(slog.New(h)) 45 | } 46 | 47 | // Init slog 48 | func Init(environment string) { 49 | 50 | switch environment { 51 | case "DEVELOPMENT": 52 | InitLogToStdoutDebug() 53 | case "TEST": 54 | InitLogToFile() 55 | case "PRODUCTION": 56 | InitLogToFile() 57 | } 58 | slog.Info("", "Environment", environment) 59 | } 60 | 61 | // Debug logs a message with debug log level. 62 | func Debug(msg string) { 63 | slog.Debug(msg) 64 | } 65 | 66 | // Debugf logs a formatted message with debug log level. 67 | func Debugf(msg string, args ...interface{}) { 68 | slog.Debug(msg, args...) 69 | } 70 | 71 | // Info logs a message with info log level. 72 | func Info(msg string) { 73 | slog.Info(msg) 74 | } 75 | 76 | // Infof logs a formatted message with info log level. 77 | func Infof(msg string, args ...interface{}) { 78 | slog.Info(msg, args...) 79 | } 80 | 81 | // Warn logs a message with warn log level. 82 | func Warn(msg string) { 83 | slog.Warn(msg) 84 | } 85 | 86 | // Warnf logs a formatted message with warn log level. 87 | func Warnf(msg string, args ...interface{}) { 88 | slog.Warn(msg, args...) 89 | } 90 | 91 | // Error logs a message with error log level. 92 | func Error(msg string, err error) { 93 | slog.Error(msg, err) 94 | } 95 | 96 | // Errorf logs a formatted message with error log level. 97 | func Errorf(msg string, err error, args ...any) { 98 | slog.Error(msg, err, args) 99 | } 100 | 101 | // log response body data for debugging 102 | func DebugResponse(response *http.Response) string { 103 | bodyBuffer := make([]byte, 5000) 104 | var str string 105 | count, err := response.Body.Read(bodyBuffer) 106 | for ; count > 0; count, err = response.Body.Read(bodyBuffer) { 107 | if err != nil { 108 | } 109 | str += string(bodyBuffer[:count]) 110 | } 111 | slog.Debug("response data : %v", str) 112 | return str 113 | } 114 | -------------------------------------------------------------------------------- /util/modelHelper/modelHelper.go: -------------------------------------------------------------------------------- 1 | package modelHelper 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/dorajistyle/goyangi/util/log" 7 | ) 8 | 9 | func IsZeroOfUnderlyingType(x interface{}) bool { 10 | return x == reflect.Zero(reflect.TypeOf(x)).Interface() 11 | } 12 | 13 | // AssignValue assign form values to model. 14 | func AssignValue(model interface{}, form interface{}) { 15 | modelIndirect := reflect.Indirect(reflect.ValueOf(model)) 16 | formElem := reflect.ValueOf(form).Elem() 17 | typeOfTForm := formElem.Type() 18 | for i := 0; i < formElem.NumField(); i++ { 19 | modelField := modelIndirect.FieldByName(typeOfTForm.Field(i).Name) 20 | if modelField.IsValid() { 21 | formField := formElem.Field(i) 22 | modelField.Set(formField) 23 | } else { 24 | log.Warnf("modelField : %s - %s", typeOfTForm.Field(i).Name, modelField) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /util/modelHelper/modelHelper_suite_test.go: -------------------------------------------------------------------------------- 1 | package modelHelper_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestModelHelper(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "ModelHelper Suite") 13 | } 14 | -------------------------------------------------------------------------------- /util/modelHelper/modelHelper_test.go: -------------------------------------------------------------------------------- 1 | package modelHelper_test 2 | 3 | import ( 4 | . "github.com/dorajistyle/goyangi/util/modelHelper" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | type TestModel struct { 11 | ID int64 12 | Title string 13 | Order int 14 | } 15 | type TestForm struct { 16 | ID int64 17 | Title string 18 | Level int 19 | } 20 | 21 | var _ = Describe("ModelHelper", func() { 22 | var ( 23 | model *TestModel 24 | form *TestForm 25 | ) 26 | 27 | BeforeEach(func() { 28 | model = &TestModel{} 29 | form = &TestForm{ID: 4, Title: "goyangi, the beast."} 30 | }) 31 | 32 | Describe("Check the interface", func() { 33 | Context("when the type is zero", func() { 34 | It("should equal to false", func() { 35 | Expect(IsZeroOfUnderlyingType(model)).To(Equal(false)) 36 | }) 37 | }) 38 | }) 39 | Describe("Assign values of form to model", func() { 40 | Context("when values assigned successfully", func() { 41 | BeforeEach(func() { 42 | AssignValue(model, form) 43 | }) 44 | It("should have same ID", func() { 45 | Expect(model.ID).To(Equal(form.ID)) 46 | }) 47 | 48 | It("should have same title", func() { 49 | Expect(model.Title).To(Equal(form.Title)) 50 | }) 51 | }) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /util/oauth2/facebook/facebook.go: -------------------------------------------------------------------------------- 1 | package facebook 2 | 3 | import ( 4 | "net/url" 5 | 6 | oauthHelper "github.com/dorajistyle/goyangi/util/oauth2" 7 | "golang.org/x/oauth2" 8 | ) 9 | 10 | const ( 11 | ProviderId = 4 12 | Scheme = "https" 13 | Host = "graph.facebook.com" 14 | Opaque = "//graph.facebook.com/me" 15 | AuthURL = "https://www.facebook.com/dialog/oauth" 16 | TokenURL = "https://graph.facebook.com/oauth/access_token" 17 | ) 18 | 19 | var RequestURL = &url.URL{ 20 | Scheme: Scheme, 21 | Host: Host, 22 | Opaque: Opaque, 23 | } 24 | 25 | var Endpoint = oauth2.Endpoint{ 26 | AuthURL: AuthURL, 27 | TokenURL: TokenURL, 28 | } 29 | 30 | var Config = Oauth2Config() 31 | 32 | func Oauth2Config() *oauth2.Config { 33 | id, secret, redirectURL := oauthHelper.GetProvider("facebook") 34 | return &oauth2.Config{ 35 | ClientID: id, 36 | ClientSecret: secret, 37 | RedirectURL: redirectURL, 38 | Scopes: []string{ 39 | "public_profile", 40 | "email", 41 | }, 42 | Endpoint: Endpoint, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /util/oauth2/github/github.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "net/url" 5 | 6 | oauthHelper "github.com/dorajistyle/goyangi/util/oauth2" 7 | "golang.org/x/oauth2" 8 | ) 9 | 10 | const ( 11 | ProviderId = 2 12 | Scheme = "https" 13 | Host = "api.github.com" 14 | Opaque = "//api.github.com/user" 15 | AuthURL = "https://github.com/login/oauth/authorize" 16 | TokenURL = "https://github.com/login/oauth/access_token" 17 | ) 18 | 19 | var RequestURL = &url.URL{ 20 | Scheme: Scheme, 21 | Host: Host, 22 | Opaque: Opaque, 23 | } 24 | 25 | var Endpoint = oauth2.Endpoint{ 26 | AuthURL: AuthURL, 27 | TokenURL: TokenURL, 28 | } 29 | 30 | var Config = Oauth2Config() 31 | 32 | func Oauth2Config() *oauth2.Config { 33 | id, secret, redirectURL := oauthHelper.GetProvider("github") 34 | return &oauth2.Config{ 35 | ClientID: id, 36 | ClientSecret: secret, 37 | RedirectURL: redirectURL, 38 | Scopes: []string{ 39 | "user", 40 | "user:email", 41 | }, 42 | Endpoint: Endpoint, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /util/oauth2/google/google.go: -------------------------------------------------------------------------------- 1 | package google 2 | 3 | import ( 4 | "net/url" 5 | 6 | oauthHelper "github.com/dorajistyle/goyangi/util/oauth2" 7 | "golang.org/x/oauth2" 8 | ) 9 | 10 | const ( 11 | ProviderId = 1 12 | Scheme = "https" 13 | Host = "www.googleapis.com" 14 | Opaque = "//www.googleapis.com/plus/v1/people/me" 15 | AuthURL = "https://accounts.google.com/o/oauth2/auth" 16 | TokenURL = "https://accounts.google.com/o/oauth2/token" 17 | ) 18 | 19 | var RequestURL = &url.URL{ 20 | Scheme: Scheme, 21 | Host: Host, 22 | Opaque: Opaque, 23 | } 24 | 25 | var Endpoint = oauth2.Endpoint{ 26 | AuthURL: AuthURL, 27 | TokenURL: TokenURL, 28 | } 29 | 30 | var Config = Oauth2Config() 31 | 32 | func Oauth2Config() *oauth2.Config { 33 | id, secret, redirectURL := oauthHelper.GetProvider("google") 34 | return &oauth2.Config{ 35 | ClientID: id, 36 | ClientSecret: secret, 37 | RedirectURL: redirectURL, 38 | Scopes: []string{ 39 | "email", 40 | "profile", 41 | "https://www.googleapis.com/auth/plus.login", 42 | "https://www.googleapis.com/auth/plus.profile.emails.read", 43 | }, 44 | Endpoint: Endpoint, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /util/oauth2/helper.go: -------------------------------------------------------------------------------- 1 | // Helper functions of oauth2. 2 | 3 | package oauth2 4 | 5 | import ( 6 | "net/url" 7 | "strings" 8 | 9 | "github.com/dorajistyle/goyangi/util/log" 10 | "github.com/spf13/viper" 11 | ) 12 | 13 | func GetProvider(provider string) (id string, secret string, redirectURL string) { 14 | // Get provider id, secret, redirectURL from configuration. 15 | configRoot := []string{"oauth.", provider} 16 | configId := append(configRoot, ".id") 17 | configSecret := append(configRoot, ".secret") 18 | id = viper.GetString(strings.Join(configId, "")) 19 | secret = viper.GetString(strings.Join(configSecret, "")) 20 | redirectURL, err := url.JoinPath(viper.GetString("api.hostURL"), viper.GetString("api.url"), "oauth", provider, "redirect") 21 | if err != nil { 22 | log.Error("Redirect url generation failed.", err) 23 | } 24 | return id, secret, redirectURL 25 | } 26 | -------------------------------------------------------------------------------- /util/oauth2/kakao/kakao.go: -------------------------------------------------------------------------------- 1 | package kakao 2 | 3 | import ( 4 | "net/url" 5 | 6 | oauthHelper "github.com/dorajistyle/goyangi/util/oauth2" 7 | "golang.org/x/oauth2" 8 | ) 9 | 10 | const ( 11 | ProviderId = 7 12 | Scheme = "https" 13 | Host = "kapi.kakao.com" 14 | Opaque = "//kapi.kakao.com/v1/user/me" 15 | AuthURL = "https://kauth.kakao.com/oauth/authorize" 16 | TokenURL = "https://kauth.kakao.com/oauth/token" 17 | ) 18 | 19 | var RequestURL = &url.URL{ 20 | Scheme: Scheme, 21 | Host: Host, 22 | Opaque: Opaque, 23 | } 24 | 25 | var Endpoint = oauth2.Endpoint{ 26 | AuthURL: AuthURL, 27 | TokenURL: TokenURL, 28 | } 29 | 30 | var Config = Oauth2Config() 31 | 32 | func Oauth2Config() *oauth2.Config { 33 | id, secret, redirectURL := oauthHelper.GetProvider("kakao") 34 | return &oauth2.Config{ 35 | ClientID: id, 36 | ClientSecret: secret, 37 | RedirectURL: redirectURL, 38 | Scopes: []string{ 39 | "Basic_Profile", 40 | }, 41 | Endpoint: Endpoint, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /util/oauth2/linkedin/linkedin.go: -------------------------------------------------------------------------------- 1 | package linkedin 2 | 3 | import ( 4 | "net/url" 5 | 6 | oauthHelper "github.com/dorajistyle/goyangi/util/oauth2" 7 | "golang.org/x/oauth2" 8 | ) 9 | 10 | const ( 11 | ProviderId = 6 12 | Scheme = "https" 13 | Host = "api.linkedin.com" 14 | Opaque = "//api.linkedin.com/v1/people/~:(id,first-name,email-address,picture-url,public-profile-url)?format=json" 15 | AuthURL = "https://www.linkedin.com/uas/oauth2/authorization" 16 | TokenURL = "https://www.linkedin.com/uas/oauth2/accessToken" 17 | ) 18 | 19 | var RequestURL = &url.URL{ 20 | Scheme: Scheme, 21 | Host: Host, 22 | Opaque: Opaque, 23 | } 24 | 25 | var Endpoint = oauth2.Endpoint{ 26 | AuthURL: AuthURL, 27 | TokenURL: TokenURL, 28 | } 29 | 30 | var Config = Oauth2Config() 31 | 32 | func Oauth2Config() *oauth2.Config { 33 | id, secret, redirectURL := oauthHelper.GetProvider("linkedin") 34 | return &oauth2.Config{ 35 | ClientID: id, 36 | ClientSecret: secret, 37 | RedirectURL: redirectURL, 38 | Scopes: []string{ 39 | "r_emailaddress", 40 | "r_basicprofile", 41 | "r_fullprofile", 42 | }, 43 | Endpoint: Endpoint, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /util/oauth2/naver/naver.go: -------------------------------------------------------------------------------- 1 | package naver 2 | 3 | import ( 4 | "net/url" 5 | 6 | oauthHelper "github.com/dorajistyle/goyangi/util/oauth2" 7 | "golang.org/x/oauth2" 8 | ) 9 | 10 | const ( 11 | ProviderId = 8 12 | Scheme = "https" 13 | Host = "apis.naver.com" 14 | Opaque = "//apis.naver.com/nidlogin/nid/getUserProfile.xml" 15 | AuthURL = "https://nid.naver.com/oauth2.0/authorize" 16 | TokenURL = "https://nid.naver.com//oauth2.0/token" 17 | ) 18 | 19 | var RequestURL = &url.URL{ 20 | Scheme: Scheme, 21 | Host: Host, 22 | Opaque: Opaque, 23 | } 24 | 25 | var Endpoint = oauth2.Endpoint{ 26 | AuthURL: AuthURL, 27 | TokenURL: TokenURL, 28 | } 29 | 30 | var Config = Oauth2Config() 31 | 32 | func Oauth2Config() *oauth2.Config { 33 | id, secret, redirectURL := oauthHelper.GetProvider("naver") 34 | return &oauth2.Config{ 35 | ClientID: id, 36 | ClientSecret: secret, 37 | RedirectURL: redirectURL, 38 | Scopes: []string{ 39 | "user", 40 | "user:email", 41 | }, 42 | Endpoint: Endpoint, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /util/oauth2/oauth2.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package oauth2 contains Martini handlers to provide 16 | // user login via an OAuth 2.0 backend. 17 | package oauth2 18 | 19 | import ( 20 | "net/http" 21 | "net/url" 22 | 23 | "github.com/dorajistyle/goyangi/util/log" 24 | "golang.org/x/oauth2" 25 | ) 26 | 27 | type AuthResponse struct { 28 | State string `form:"state"` 29 | Code string `form:"code"` 30 | Authuser int `form:"authuser"` 31 | NumSessions int `form:"num_sessions"` 32 | prompt string `form:"prompt"` 33 | ImageName string `form:"imageName"` 34 | SessionState string `form:"session_state"` 35 | } 36 | 37 | // Your credentials should be obtained from the Google 38 | // Developer Console (https://console.developers.google.com). 39 | func OauthURL(conf *oauth2.Config) string { 40 | // Redirect user to Google's consent page to ask for permission 41 | // for the scopes specified above. 42 | 43 | url := conf.AuthCodeURL("state") 44 | log.Debugf("\nVisit the URL for the auth dialog: %v\n", url) 45 | return url 46 | } 47 | 48 | func OauthRequest(url *url.URL, conf *oauth2.Config, authResponse AuthResponse) (*http.Response, *oauth2.Token, error) { 49 | var res *http.Response 50 | var req *http.Request 51 | var token *oauth2.Token 52 | var err error 53 | 54 | log.Info("Oauth Redirect performed.") 55 | // Handle the exchange code to initiate a transport. 56 | log.Debugf("AuthResponse :%v\n", authResponse) 57 | token, err = conf.Exchange(oauth2.NoContext, authResponse.Code) 58 | // log.Debugf("Token AccessToken :%s\n", token.AccessToken) 59 | // log.Debugf("Token TokenType :%s\n", token.TokenType) 60 | // log.Debugf("Token RefreshToken :%s\n", token.RefreshToken) 61 | // log.Debugf("Token Expiry :%s\n", token.Expiry) 62 | if err != nil { 63 | return res, token, err 64 | } 65 | // if err == nil { 66 | client := conf.Client(oauth2.NoContext, token) 67 | log.Debugf("url : %s\n", url.String()) 68 | req, err = http.NewRequest("GET", "url.String()", nil) 69 | if err != nil { 70 | return res, token, err 71 | } 72 | // if err == nil { 73 | req.URL = url 74 | res, err = client.Do(req) 75 | // if err != nil { 76 | // return res, token, err 77 | // } 78 | return res, token, nil 79 | // if err == nil { 80 | // return res, token, err 81 | // } 82 | // } 83 | // } 84 | 85 | // return nil, token, err 86 | 87 | } 88 | -------------------------------------------------------------------------------- /util/oauth2/twitter/twitter.go: -------------------------------------------------------------------------------- 1 | package twitter 2 | 3 | import ( 4 | "net/url" 5 | 6 | oauthHelper "github.com/dorajistyle/goyangi/util/oauth2" 7 | "golang.org/x/oauth2" 8 | ) 9 | 10 | const ( 11 | ProviderId = 5 12 | Scheme = "https" 13 | Host = "api.twitter.com" 14 | Opaque = "//api.twitter.com/1.1/account/verify_credentials.json" 15 | AuthURL = "https://api.twitter.com/oauth/authenticate" 16 | TokenURL = "https://api.twitter.com/oauth/access_token" 17 | ) 18 | 19 | var RequestURL = &url.URL{ 20 | Scheme: Scheme, 21 | Host: Host, 22 | Opaque: Opaque, 23 | } 24 | 25 | var Endpoint = oauth2.Endpoint{ 26 | AuthURL: AuthURL, 27 | TokenURL: TokenURL, 28 | } 29 | 30 | var Config = Oauth2Config() 31 | 32 | func Oauth2Config() *oauth2.Config { 33 | id, secret, redirectURL := oauthHelper.GetProvider("twitter") 34 | return &oauth2.Config{ 35 | ClientID: id, 36 | ClientSecret: secret, 37 | RedirectURL: redirectURL, 38 | // Scopes: []string{ 39 | // , 40 | // }, 41 | Endpoint: Endpoint, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /util/oauth2/yahoo/yahoo.go: -------------------------------------------------------------------------------- 1 | package yahoo 2 | 3 | import ( 4 | "net/url" 5 | 6 | oauthHelper "github.com/dorajistyle/goyangi/util/oauth2" 7 | "golang.org/x/oauth2" 8 | ) 9 | 10 | const ( 11 | ProviderId = 3 12 | Scheme = "https" 13 | Host = "social.yahooapis.com" 14 | Opaque = "//social.yahooapis.com/v1/user" 15 | AuthURL = "https://api.login.yahoo.com/oauth2/request_auth" 16 | TokenURL = "https://api.login.yahoo.com/oauth2/get_token" 17 | ) 18 | 19 | var RequestURL = &url.URL{ 20 | Scheme: Scheme, 21 | Host: Host, 22 | Opaque: Opaque, 23 | } 24 | 25 | var Endpoint = oauth2.Endpoint{ 26 | AuthURL: AuthURL, 27 | TokenURL: TokenURL, 28 | } 29 | 30 | var Config = Oauth2Config() 31 | 32 | func Oauth2Config() *oauth2.Config { 33 | id, secret, redirectURL := oauthHelper.GetProvider("yahoo") 34 | return &oauth2.Config{ 35 | ClientID: id, 36 | ClientSecret: secret, 37 | RedirectURL: redirectURL, 38 | Scopes: []string{ 39 | "Read (Shared) Yahoo Profiles", 40 | }, 41 | Endpoint: Endpoint, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /util/pagination/pagination.go: -------------------------------------------------------------------------------- 1 | package pagination 2 | 3 | func Paginate(currentPage int, perPage int, total int) (int, int, bool, bool) { 4 | var hasPrev, hasNext bool 5 | var offset int 6 | if currentPage == 0 { 7 | currentPage = 1 8 | } 9 | offset = (currentPage - 1) * perPage 10 | if currentPage > 1 { 11 | hasPrev = true 12 | } 13 | if total > (currentPage * perPage) { 14 | hasNext = true 15 | } 16 | return offset, currentPage, hasPrev, hasNext 17 | } 18 | -------------------------------------------------------------------------------- /util/pagination/pagination_suite_test.go: -------------------------------------------------------------------------------- 1 | package pagination_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestPagination(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Pagination Suite") 13 | } 14 | -------------------------------------------------------------------------------- /util/pagination/pagination_test.go: -------------------------------------------------------------------------------- 1 | package pagination_test 2 | 3 | import ( 4 | . "github.com/dorajistyle/goyangi/util/pagination" 5 | viper "github.com/dorajistyle/goyangi/util/viper" 6 | 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | ) 10 | 11 | func init() { 12 | viper.LoadConfig() 13 | } 14 | 15 | var _ = Describe("Pagination", func() { 16 | var ( 17 | offset int 18 | currentPage int 19 | perPage int 20 | total int 21 | hasPrev bool 22 | hasNext bool 23 | ) 24 | 25 | BeforeEach(func() { 26 | currentPage = 2 27 | perPage = 10 28 | total = 17 29 | }) 30 | 31 | Describe("Paginate items", func() { 32 | Context("when ", func() { 33 | BeforeEach(func() { 34 | offset, currentPage, hasPrev, hasNext = Paginate(currentPage, perPage, total) 35 | }) 36 | 37 | It("should be 5", func() { 38 | Expect(offset).To(Equal(10)) 39 | }) 40 | 41 | It("should be 2", func() { 42 | Expect(currentPage).To(Equal(2)) 43 | }) 44 | 45 | It("should be true", func() { 46 | Expect(hasPrev).To(Equal(true)) 47 | }) 48 | 49 | It("should be false", func() { 50 | Expect(hasNext).To(Equal(false)) 51 | }) 52 | }) 53 | }) 54 | 55 | }) 56 | -------------------------------------------------------------------------------- /util/random/random.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | const ( 9 | Letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!#$%&*+=?@^_|-" 10 | ) 11 | 12 | func GenerateRandomString(n int) string { 13 | letters := []rune(Letters) 14 | rand.Seed(time.Now().UTC().UnixNano()) 15 | randomString := make([]rune, n) 16 | for i := range randomString { 17 | randomString[i] = letters[rand.Intn(len(letters))] 18 | } 19 | return string(randomString) 20 | } 21 | -------------------------------------------------------------------------------- /util/random/random_suite_test.go: -------------------------------------------------------------------------------- 1 | package random_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestRandom(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Random Suite") 13 | } 14 | -------------------------------------------------------------------------------- /util/random/random_test.go: -------------------------------------------------------------------------------- 1 | package random_test 2 | 3 | import ( 4 | . "github.com/dorajistyle/goyangi/util/random" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | var _ = Describe("Random", func() { 11 | var ( 12 | n int 13 | randomString string 14 | ) 15 | 16 | BeforeEach(func() { 17 | randomString = GenerateRandomString(n) 18 | }) 19 | 20 | Describe("Generate random string", func() { 21 | Context("when random string generated successfully", func() { 22 | It("should have length n", func() { 23 | Expect(len(randomString)).To(Equal(n)) 24 | }) 25 | 26 | }) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /util/redis/cache.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | 7 | "github.com/dorajistyle/goyangi/util/log" 8 | "github.com/dorajistyle/goyangi/util/stringHelper" 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | // responseGenerator types take an *gin.Context and return a string, an int and an error value. 13 | type responseGenerator func(c *gin.Context) (string, int, error) 14 | 15 | // CacheResponse caches a response if redis server is available. 16 | func CacheResponse(c *gin.Context, keyPrefix string, keyBody string, resGenerator responseGenerator) (string, int, error) { 17 | var encrypted string 18 | var status int 19 | var resErr error 20 | var cacheStr string 21 | var cacheKey string 22 | var cacheErr error 23 | cacheErr = errors.New("It is a default error of cache") 24 | cacheKey = stringHelper.ConcatString(keyPrefix, keyBody) 25 | cacheStr, cacheErr = Get(cacheKey) 26 | 27 | if cacheErr != nil { 28 | if resGenerator == nil { 29 | log.Debug("resGenerator is nil") 30 | return encrypted, status, cacheErr 31 | } 32 | encrypted, status, resErr = resGenerator(c) 33 | 34 | log.Debugf("cacheKey : %s\n", cacheKey) 35 | appendErr := Append(cacheKey, encrypted) 36 | if appendErr != nil { 37 | log.Error("Cannot append cache", appendErr) 38 | } 39 | 40 | } else { 41 | log.Debugf("It has cached response. Key : %s\n", cacheKey) 42 | encrypted = cacheStr 43 | status = http.StatusOK 44 | } 45 | return encrypted, status, resErr 46 | } 47 | -------------------------------------------------------------------------------- /util/redis/redis.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | // "github.com/dorajistyle/goyangi/util/log" 5 | 6 | "github.com/dorajistyle/goyangi/util/log" 7 | "github.com/rueian/rueidis" 8 | "github.com/spf13/viper" 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | func GetClient() rueidis.Client { 13 | redisAddr := viper.GetString("redis.addr") + ":" + viper.GetString("redis.port") 14 | options := rueidis.ClientOption{ 15 | InitAddress: []string{redisAddr}, 16 | } 17 | client, err := rueidis.NewClient(options) 18 | if err != nil { 19 | log.Error("Cannot get ruedis client.", err) 20 | } 21 | return client 22 | } 23 | 24 | func Append(key string, value string) error { 25 | client := GetClient() 26 | defer client.Close() 27 | cmd := client.B().Set().Key(key).Value(value).Build() 28 | res := client.Do(context.Background(), cmd) 29 | return res.Error() 30 | } 31 | 32 | func Get(key string) (value string, err error) { 33 | client := GetClient() 34 | defer client.Close() 35 | cmd := client.B().Get().Key(key).Build() 36 | res := client.Do(context.Background(), cmd) 37 | value, _ = res.ToString() 38 | return value, res.Error() 39 | } 40 | 41 | func Del(key string) error { 42 | client := GetClient() 43 | defer client.Close() 44 | cmd := client.B().Del().Key(key).Build() 45 | res := client.Do(context.Background(), cmd) 46 | return res.Error() 47 | } 48 | -------------------------------------------------------------------------------- /util/redis/redis_suite_test.go: -------------------------------------------------------------------------------- 1 | package redis_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestRedis(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Redis Suite") 13 | } 14 | -------------------------------------------------------------------------------- /util/redis/redis_test.go: -------------------------------------------------------------------------------- 1 | package redis_test 2 | 3 | import ( 4 | . "github.com/dorajistyle/goyangi/util/redis" 5 | viper "github.com/dorajistyle/goyangi/util/viper" 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func init() { 11 | viper.LoadConfig() 12 | } 13 | 14 | var _ = Describe("Redis", func() { 15 | var ( 16 | bestSDK string 17 | worstSDK string 18 | ) 19 | 20 | BeforeEach(func() { 21 | 22 | bestSDK = "Goyangi SDK" 23 | worstSDK = "Goyak SDK" 24 | Append("bestSDKEver", bestSDK) 25 | Append("worstSDKEver", worstSDK) 26 | }) 27 | Describe("get best sdk", func() { 28 | 29 | bestSDKEver, err := Get("bestSDKEver") 30 | // fmt.Println("bestSDKEver: %s", bestSDKEver) 31 | Context("when redis get a bestSDKEver successfully", func() { 32 | It("should equals with Goyangi SDK", func() { 33 | Expect(bestSDKEver).To(Equal(bestSDK)) 34 | }) 35 | It("should have no error", func() { 36 | Expect(err).To(BeNil()) 37 | }) 38 | 39 | }) 40 | }) 41 | Describe("get worst sdk", func() { 42 | worstSdkEver, err := Get("worstSDKEver") 43 | Context("when redis get a worstSDKEver successfully", func() { 44 | It("should equals with NowWorst SDK", func() { 45 | Expect(worstSdkEver).To(Equal(worstSDK)) 46 | }) 47 | It("should have no error", func() { 48 | Expect(err).To(BeNil()) 49 | }) 50 | 51 | }) 52 | }) 53 | Describe("get worst sdk after delete key", func() { 54 | delErr := Del("worstSDKEver") 55 | Context("when redis del a worstSDKEver successfully", func() { 56 | It("should have no error", func() { 57 | Expect(delErr).To(BeNil()) 58 | }) 59 | }) 60 | worstSDKEver, err := Get("worstSDKEver") 61 | Context("when redis get a worstSDKEver successfully", func() { 62 | It("should equals with NowWorst SDK", func() { 63 | Expect(worstSDKEver).To(BeEmpty()) 64 | }) 65 | It("should have no error", func() { 66 | Expect(err).ToNot(BeNil()) 67 | }) 68 | 69 | }) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /util/retrieveHelper/app.go: -------------------------------------------------------------------------------- 1 | package retrieveHelper 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/dorajistyle/goyangi/db" 7 | "github.com/dorajistyle/goyangi/model" 8 | ) 9 | 10 | func RetriveAppWithAppKey(appKey string) (model.App, error) { 11 | var app model.App 12 | if db.ORM.Where("key=?", appKey).First(&app).RecordNotFound() { 13 | return app, errors.New("App does not exist.") 14 | } 15 | return app, nil 16 | } 17 | -------------------------------------------------------------------------------- /util/retrieveHelper/user.go: -------------------------------------------------------------------------------- 1 | package retrieveHelper 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/dorajistyle/goyangi/db" 7 | "github.com/dorajistyle/goyangi/model" 8 | ) 9 | 10 | // RetriveUserWithAppIdAndUserName retrieve an user with AppId and user's name 11 | func RetriveUserWithAppIdAndUserName(appId int64, userName string) (model.User, error) { 12 | var user model.User 13 | if db.ORM.Where("app_id=? and name=?", appId, userName).First(&user).RecordNotFound() { 14 | return user, errors.New("User not found. (Check the AppId and the UserName)") 15 | } 16 | return user, nil 17 | } 18 | -------------------------------------------------------------------------------- /util/stringHelper/stringHelper.go: -------------------------------------------------------------------------------- 1 | package stringHelper 2 | 3 | import "bytes" 4 | 5 | // Concat concatenates string to buffer. 6 | // According to 'Efficient String Concatenation in Go(http://herman.asia/efficient-string-concatenation-in-go)', 7 | // bytes.Buffer is best choice for heavy-duty case. 8 | // You should call buffer.String() to get a concatenated string after all concaternating finished. 9 | func Concat(buffer *bytes.Buffer, str string) { 10 | buffer.WriteString(str) 11 | } 12 | 13 | // ConcatExist concatenates string to string array. 14 | // According to 'Efficient String Concatenation in Go(http://herman.asia/efficient-string-concatenation-in-go)', 15 | // When str is already exist, it's faster than buffer concatenation. 16 | // You should call strings.Join(strs, "") to get a concatenated string after all concaternating finished. 17 | func ConcatExist(strs []string, str string) []string { 18 | return append(strs, str) 19 | } 20 | 21 | // ConcatString concatenates leadingStr and followingStr. 22 | func ConcatString(leadingStr string, followingStr string) string { 23 | var buffer *bytes.Buffer 24 | buffer = new(bytes.Buffer) 25 | buffer.WriteString(leadingStr) 26 | buffer.WriteString(followingStr) 27 | return buffer.String() 28 | } -------------------------------------------------------------------------------- /util/stringHelper/stringHelper_suite_test.go: -------------------------------------------------------------------------------- 1 | package stringHelper_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestStringHelper(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "StringHelper Suite") 13 | } 14 | -------------------------------------------------------------------------------- /util/stringHelper/stringHelper_test.go: -------------------------------------------------------------------------------- 1 | package stringHelper_test 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | 7 | . "github.com/dorajistyle/goyangi/util/stringHelper" 8 | viper "github.com/dorajistyle/goyangi/util/viper" 9 | 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | func init() { 15 | viper.LoadConfig() 16 | } 17 | 18 | var _ = Describe("StringHelper", func() { 19 | var ( 20 | testStr string 21 | strArr []string 22 | lengthOfTestStr int 23 | lengthOfStrArr int 24 | joinedStr string 25 | leadingStr string 26 | followingStr string 27 | concatedString string 28 | concatedLength int 29 | buffer *bytes.Buffer 30 | ) 31 | 32 | BeforeEach(func() { 33 | testStr = "Goyangi is a SDK." 34 | leadingStr = "Tanguero y " 35 | followingStr = "Tanguera" 36 | }) 37 | 38 | Describe("Concat string using Concat", func() { 39 | BeforeEach(func() { 40 | buffer = new(bytes.Buffer) 41 | Concat(buffer, testStr) 42 | }) 43 | Context("when testStr concatenates successfully", func() { 44 | It("should have length concatedLength", func() { 45 | Expect(len(buffer.String())).To(Equal(len(testStr))) 46 | }) 47 | }) 48 | }) 49 | 50 | Describe("Concat string using ConcatExist", func() { 51 | BeforeEach(func() { 52 | lengthOfStrArr = 1 53 | strArr = []string{} 54 | strArr = ConcatExist(strArr, testStr) 55 | lengthOfTestStr = len(testStr) 56 | joinedStr = strings.Join(strArr, "") 57 | }) 58 | Context("when testStr appended to strArr successfully", func() { 59 | It("should have length lengthOfStrArr", func() { 60 | Expect(len(strArr)).To(Equal(lengthOfStrArr)) 61 | }) 62 | }) 63 | Context("when strArr joined successfully", func() { 64 | It("should have length lengthOfTestStr", func() { 65 | Expect(len(joinedStr)).To(Equal(lengthOfTestStr)) 66 | }) 67 | }) 68 | }) 69 | 70 | Describe("Concat string using ConcatString", func() { 71 | BeforeEach(func() { 72 | concatedString = ConcatString(leadingStr, followingStr) 73 | concatedLength = len(concatedString) 74 | }) 75 | Context("when leadingStr and followingStr concatenates successfully", func() { 76 | It("should have length concatedLength", func() { 77 | Expect(concatedLength).To(Equal(len(leadingStr) + len(followingStr))) 78 | }) 79 | }) 80 | }) 81 | 82 | }) 83 | -------------------------------------------------------------------------------- /util/timeHelper/timeHelper.go: -------------------------------------------------------------------------------- 1 | package timeHelper 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/dorajistyle/goyangi/util/log" 7 | ) 8 | 9 | func FewDaysLater(day int) time.Time { 10 | return FewDurationLater(time.Duration(day) * 24 * time.Hour) 11 | } 12 | 13 | func TwentyFourHoursLater() time.Time { 14 | return FewDurationLater(time.Duration(24) * time.Hour) 15 | } 16 | 17 | func SixHoursLater() time.Time { 18 | return FewDurationLater(time.Duration(6) * time.Hour) 19 | } 20 | 21 | func InTimeSpan(start, end, check time.Time) bool { 22 | log.Debugf("check after before: %s %t %t\n", check, check.After(start), check.Before(end)) 23 | return check.After(start) && check.Before(end) 24 | } 25 | 26 | func InTimeSpanNow(start, end time.Time) bool { 27 | now := time.Now() 28 | return InTimeSpan(start, end, now) 29 | } 30 | 31 | func FewDurationLater(duration time.Duration) time.Time { 32 | // When Save time should considering UTC 33 | // baseTime := time.Now() 34 | // log.Debugf("basetime : %s", baseTime) 35 | fewDurationLater := time.Now().Add(duration) 36 | log.Debugf("time : %s", fewDurationLater) 37 | return fewDurationLater 38 | } 39 | 40 | func FewDurationLaterMillisecond(duration time.Duration) int64 { 41 | return FewDurationLater(duration).UnixNano() / int64(time.Millisecond) 42 | } 43 | 44 | func IsExpired(expirationTime time.Time) bool { 45 | // baseTime := time.Now() 46 | // log.Debugf("basetime : %s", baseTime) 47 | log.Debugf("expirationTime : %s", expirationTime) 48 | // elapsed := time.Since(expirationTime) 49 | // log.Debugf("elapsed : %s", elapsed) 50 | after := time.Now().After(expirationTime) 51 | log.Debugf("after : %t", after) 52 | return after 53 | } 54 | -------------------------------------------------------------------------------- /util/timeHelper/timeHelper_suite_test.go: -------------------------------------------------------------------------------- 1 | package timeHelper_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestTimeHelper(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "TimeHelper Suite") 13 | } 14 | -------------------------------------------------------------------------------- /util/timeHelper/timeHelper_test.go: -------------------------------------------------------------------------------- 1 | package timeHelper_test 2 | 3 | import ( 4 | "time" 5 | 6 | . "github.com/dorajistyle/goyangi/util/timeHelper" 7 | viper "github.com/dorajistyle/goyangi/util/viper" 8 | . "github.com/onsi/ginkgo" 9 | . "github.com/onsi/gomega" 10 | ) 11 | 12 | func init() { 13 | viper.LoadConfig() 14 | } 15 | 16 | var _ = Describe("TimeHelper", func() { 17 | var ( 18 | duration time.Duration 19 | now time.Time 20 | later time.Time 21 | isExpired bool 22 | ) 23 | 24 | BeforeEach(func() { 25 | duration = time.Duration(6) * time.Hour 26 | now = time.Now() 27 | }) 28 | 29 | Describe("Get the time that few duration later", func() { 30 | 31 | Context("when getting the time successfully", func() { 32 | BeforeEach(func() { 33 | later = FewDurationLater(duration) 34 | }) 35 | 36 | It("should be 6 hours later.", func() { 37 | Expect(later.Hour()).To(Equal(now.Add(duration).Hour())) 38 | }) 39 | 40 | }) 41 | }) 42 | 43 | Describe("Check the time is expired", func() { 44 | 45 | Context("when time expiration checked successfully", func() { 46 | BeforeEach(func() { 47 | isExpired = IsExpired(now) 48 | }) 49 | 50 | It("should be expired.", func() { 51 | Expect(isExpired).To(Equal(true)) 52 | }) 53 | 54 | }) 55 | }) 56 | 57 | }) 58 | -------------------------------------------------------------------------------- /util/userHelper/user.go: -------------------------------------------------------------------------------- 1 | package userHelper 2 | 3 | import ( 4 | "github.com/dorajistyle/goyangi/db" 5 | "github.com/dorajistyle/goyangi/model" 6 | 7 | // "github.com/gin-gonic/gin" 8 | "errors" 9 | "net/http" 10 | 11 | "github.com/dorajistyle/goyangi/util/log" 12 | "github.com/dorajistyle/goyangi/util/retrieveHelper" 13 | // "github.com/dorajistyle/goyangi/util/crypto" 14 | ) 15 | 16 | // FindUserByUserName creates a user. 17 | func FindUserByUserName(appId int64, userName string) (model.User, int, error) { 18 | var user model.User 19 | var err error 20 | // token := c.Request.Header.Get("X-Auth-Token") 21 | user, err = retrieveHelper.RetriveUserWithAppIdAndUserName(appId, userName) 22 | if err != nil { 23 | return user, http.StatusUnauthorized, err 24 | } 25 | return user, http.StatusOK, nil 26 | } 27 | 28 | // FindOrCreateUser creates a user. 29 | func FindOrCreateUser(appId int64, userName string) (model.User, int, error) { 30 | var user model.User 31 | var err error 32 | 33 | // if len(token) > 0 { 34 | // log.Debug("header token exist.") 35 | // } else { 36 | // token, err = Token(c) 37 | // log.Debug("header token not exist.") 38 | // if err != nil { 39 | // return user, http.StatusUnauthorized, err 40 | // } 41 | // } 42 | log.Debugf("userName : %s\n", userName) 43 | // log.Debugf("Error : %s\n", err.Error()) 44 | user, err = retrieveHelper.RetriveUserWithAppIdAndUserName(appId, userName) 45 | if err != nil { 46 | var user model.User 47 | // return user, http.StatusBadRequest, err 48 | user.Name = userName 49 | // user.Token = token 50 | user.AppId = uint(appId) 51 | log.Debugf("user %+v\n", user) 52 | if db.ORM.Create(&user).Error != nil { 53 | return user, http.StatusBadRequest, errors.New("User is not created.") 54 | } 55 | log.Debugf("retrived User %v\n", user) 56 | return user, http.StatusOK, nil 57 | } 58 | return user, http.StatusBadRequest, nil 59 | } 60 | -------------------------------------------------------------------------------- /util/validation/validation.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/dorajistyle/goyangi/util/log" 7 | ) 8 | 9 | const EMAIL_REGEX = `(\w[-._\w]*\w@\w[-._\w]*\w\.\w{2,3})` 10 | 11 | func EmailValidation(email string) bool { 12 | exp, err := regexp.Compile(EMAIL_REGEX) 13 | if regexpCompiled := log.CheckError(err); regexpCompiled { 14 | if exp.MatchString(email) { 15 | return true 16 | } 17 | } 18 | return false 19 | } 20 | -------------------------------------------------------------------------------- /util/validation/validation_suite_test.go: -------------------------------------------------------------------------------- 1 | package validation_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestValidation(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Validation Suite") 13 | } 14 | -------------------------------------------------------------------------------- /util/validation/validation_test.go: -------------------------------------------------------------------------------- 1 | package validation_test 2 | 3 | import ( 4 | . "github.com/dorajistyle/goyangi/util/validation" 5 | viper "github.com/dorajistyle/goyangi/util/viper" 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func init() { 11 | viper.LoadConfig() 12 | } 13 | 14 | var _ = Describe("Validation", func() { 15 | var ( 16 | emailValid string 17 | emailInvalid string 18 | isValid bool 19 | ) 20 | 21 | BeforeEach(func() { 22 | emailValid = "test@goyangi.github.io" 23 | emailInvalid = "test#goyangi.github.io" 24 | }) 25 | 26 | Describe("Check that the email address is valid or not", func() { 27 | 28 | Context("when the email checked successfully", func() { 29 | BeforeEach(func() { 30 | isValid = EmailValidation(emailValid) 31 | }) 32 | 33 | It("should be valid.", func() { 34 | Expect(isValid).To(Equal(true)) 35 | }) 36 | }) 37 | 38 | Context("when the email checked successfully", func() { 39 | BeforeEach(func() { 40 | isValid = EmailValidation(emailInvalid) 41 | }) 42 | 43 | It("should be invalid.", func() { 44 | Expect(isValid).To(Equal(false)) 45 | }) 46 | }) 47 | }) 48 | 49 | }) 50 | -------------------------------------------------------------------------------- /util/viper/viper.go: -------------------------------------------------------------------------------- 1 | package viper 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/spf13/viper" 7 | ) 8 | 9 | func LoadConfig() { 10 | viper.AddConfigPath("../../.") 11 | viper.SetConfigType("yaml") 12 | 13 | // switch os.Getenv("ENV") { 14 | // case "DEVELOPMENT": 15 | // viper.SetConfigName(".env.dev") 16 | // case "TEST": 17 | // viper.SetConfigName(".env.test") 18 | // case "PRODUCTION": 19 | // viper.SetConfigName(".env.prod") 20 | // } 21 | 22 | viper.SetConfigName("config.yml") 23 | 24 | viper.AutomaticEnv() 25 | 26 | if err := viper.ReadInConfig(); err != nil { 27 | log.Fatalln("Fatal error config file", err) 28 | } 29 | } 30 | --------------------------------------------------------------------------------