├── LICENSE ├── README.md ├── cmd └── go-api │ └── main.go ├── config └── config.json ├── docker-compose.yml ├── go.mod ├── go.sum ├── interval ├── config │ └── config.go ├── middleware │ └── is_auth.go ├── models │ ├── story.go │ └── user.go ├── story │ ├── api.go │ ├── handler.go │ ├── repository.go │ └── service.go └── user │ ├── api.go │ ├── handler.go │ ├── repository.go │ └── service.go └── pkg └── errors └── errors.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Tunar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 🌎 Starter Code 2 | 3 | 🚀 This is a starter kit for a Go web application. It includes: 4 | 5 | - [x] Fiber is Fast, simple, and minimalist web framework for Go 6 | 7 | - [x] MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era. 8 | 9 | - [x] Session middleware for Fiber 10 | 11 | - [x] Clean Architecture in Go 12 | -------------------------------------------------------------------------------- /cmd/go-api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/gofiber/fiber/v2" 8 | "github.com/gofiber/fiber/v2/middleware/cors" 9 | "github.com/gofiber/fiber/v2/middleware/session" 10 | "github.com/tunardev/go-api-boilerplate/interval/config" 11 | "github.com/tunardev/go-api-boilerplate/interval/story" 12 | "github.com/tunardev/go-api-boilerplate/interval/user" 13 | "go.mongodb.org/mongo-driver/mongo" 14 | "go.mongodb.org/mongo-driver/mongo/options" 15 | ) 16 | 17 | func main() { 18 | // Load config 19 | cfg, err := config.Load("./config/config.json") 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | // Connect to MongoDB 25 | client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(cfg.Mongo_URI)) 26 | if err != nil { 27 | panic(err) 28 | } 29 | db := client.Database("project") 30 | defer func () { // Close connection when main() exits 31 | if err := client.Disconnect(context.TODO()); err != nil { 32 | panic(err) 33 | } 34 | }() 35 | 36 | // Build web app 37 | app := fiber.New() 38 | store := session.New() 39 | 40 | // initalize middlewares 41 | app.Use(cors.New(cors.Config{ 42 | AllowOrigins: "*", 43 | AllowHeaders: "Origin, Content-Type, Accept", 44 | })) 45 | 46 | // initalize routes 47 | story.Handler(app, db.Collection("stories"), store) 48 | user.Handler(app, db.Collection("users"), store) 49 | 50 | // Start server 51 | app.Listen(fmt.Sprintf(":%v", cfg.Port)) 52 | } -------------------------------------------------------------------------------- /config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 8080, 3 | "mongodb": "mongodb://localhost:27017" 4 | } 5 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | services: 3 | mongodb: 4 | image: mongo:latest 5 | restart: always 6 | ports: 7 | - 27017:27017 8 | volumes: 9 | - data:/data/db 10 | volumes: 11 | data: 12 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tunardev/go-api-boilerplate 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/andybalholm/brotli v1.0.4 // indirect 7 | github.com/gofiber/fiber/v2 v2.42.0 // indirect 8 | github.com/golang/snappy v0.0.1 // indirect 9 | github.com/google/uuid v1.3.0 // indirect 10 | github.com/klauspost/compress v1.15.9 // indirect 11 | github.com/mattn/go-colorable v0.1.13 // indirect 12 | github.com/mattn/go-isatty v0.0.17 // indirect 13 | github.com/mattn/go-runewidth v0.0.14 // indirect 14 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect 15 | github.com/philhofer/fwd v1.1.1 // indirect 16 | github.com/pkg/errors v0.9.1 // indirect 17 | github.com/rivo/uniseg v0.2.0 // indirect 18 | github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect 19 | github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect 20 | github.com/tinylib/msgp v1.1.6 // indirect 21 | github.com/valyala/bytebufferpool v1.0.0 // indirect 22 | github.com/valyala/fasthttp v1.44.0 // indirect 23 | github.com/valyala/tcplisten v1.0.0 // indirect 24 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 25 | github.com/xdg-go/scram v1.1.1 // indirect 26 | github.com/xdg-go/stringprep v1.0.3 // indirect 27 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect 28 | go.mongodb.org/mongo-driver v1.11.2 // indirect 29 | golang.org/x/crypto v0.6.0 // indirect 30 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect 31 | golang.org/x/sys v0.5.0 // indirect 32 | golang.org/x/text v0.7.0 // indirect 33 | ) -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 2 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/gofiber/fiber/v2 v2.42.0 h1:Fnp7ybWvS+sjNQsFvkhf4G8OhXswvB6Vee8hM/LyS+8= 6 | github.com/gofiber/fiber/v2 v2.42.0/go.mod h1:3+SGNjqMh5VQH5Vz2Wdi43zTIV16ktlFd3x3R6O1Zlc= 7 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= 8 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 9 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 10 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 11 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 12 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 13 | github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= 14 | github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= 15 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 16 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 17 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 18 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 19 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 20 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 21 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 22 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 23 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= 24 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 25 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= 26 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 27 | github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ= 28 | github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= 29 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 30 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 31 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 32 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 33 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 34 | github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4= 35 | github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8= 36 | github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d h1:Q+gqLBOPkFGHyCJxXMRqtUgUbTjI8/Ze8vu8GGyNFwo= 37 | github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= 38 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 39 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 40 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 41 | github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw= 42 | github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw= 43 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 44 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 45 | github.com/valyala/fasthttp v1.44.0 h1:R+gLUhldIsfg1HokMuQjdQ5bh9nuXHPIfvkYUu9eR5Q= 46 | github.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= 47 | github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= 48 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 49 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 50 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 51 | github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= 52 | github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= 53 | github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs= 54 | github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= 55 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= 56 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 57 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 58 | go.mongodb.org/mongo-driver v1.11.2 h1:+1v2rDQUWNcGW7/7E0Jvdz51V38XXxJfhzbV17aNHCw= 59 | go.mongodb.org/mongo-driver v1.11.2/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= 60 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 61 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 62 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 63 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 64 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= 65 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 66 | golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= 67 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 68 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 69 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 70 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 71 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 72 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 73 | golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 74 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 75 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 76 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 77 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 78 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 79 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 80 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 81 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 82 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 83 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 84 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 85 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= 86 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 87 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 88 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 89 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 90 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 91 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 92 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 93 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 94 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 95 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 96 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 97 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 98 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 99 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 100 | golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 101 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 102 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 103 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 104 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 105 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 106 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 107 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 108 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -------------------------------------------------------------------------------- /interval/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | ) 7 | 8 | const ( 9 | defaultPort = 8080 10 | Mongo_URI = "mongodb://localhost:27017" 11 | ) 12 | 13 | // Config is the configuration for the app service. 14 | type Config struct { 15 | Port int `json:"port"` 16 | Mongo_URI string `json:"mongodb"` 17 | } 18 | 19 | // Load loads the configuration from the given file. 20 | func Load(file string) (*Config, error) { 21 | c := Config{ 22 | Port: defaultPort, 23 | } 24 | 25 | // load from JSON config file 26 | bytes, err := ioutil.ReadFile(file) 27 | if err != nil { 28 | return nil, err 29 | } 30 | if err = json.Unmarshal(bytes, &c); err != nil { 31 | return nil, err 32 | } 33 | 34 | return &c, nil 35 | } -------------------------------------------------------------------------------- /interval/middleware/is_auth.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gofiber/fiber/v2" 7 | "github.com/gofiber/fiber/v2/middleware/session" 8 | "github.com/tunardev/go-api-boilerplate/pkg/errors" 9 | ) 10 | 11 | func IsAuth(store *session.Store) fiber.Handler { 12 | return func(c *fiber.Ctx) error { 13 | // Get the session. 14 | sess, err := store.Get(c) 15 | if err != nil { 16 | return c.Status(http.StatusInternalServerError).JSON(errors.InternalServerError(err.Error())) 17 | } 18 | 19 | // Get the user ID from the session. 20 | userID := sess.Get("userID") 21 | if userID != nil { 22 | // Set the user ID in the context. 23 | c.Locals("userID", userID) 24 | return c.Next() 25 | } 26 | 27 | // Return unauthorized. 28 | return c.Status(http.StatusUnauthorized).JSON(errors.Unauthorized("")) 29 | } 30 | } -------------------------------------------------------------------------------- /interval/models/story.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "go.mongodb.org/mongo-driver/bson/primitive" 7 | ) 8 | 9 | // Story is the record for a story. 10 | type Story struct { 11 | ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` 12 | UserID primitive.ObjectID `bson:"user_id,omitempty" json:"user_id,omitempty"` 13 | Title string `bson:"title,omitempty" json:"title,omitempty"` 14 | Text string `bson:"text,omitempty" json:"text,omitempty"` 15 | CreatedAt time.Time `bson:"created_at,omitempty" json:"created_at,omitempty"` 16 | UpdatedAt time.Time `bson:"updated_at,omitempty" json:"updated_at,omitempty"` 17 | } 18 | 19 | // Validate validates the story. 20 | func (s Story) Validate() bool { 21 | if (s.Title == "") || (s.Text == "") { 22 | return false 23 | } 24 | 25 | return true 26 | } -------------------------------------------------------------------------------- /interval/models/user.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "errors" 5 | "regexp" 6 | "time" 7 | 8 | "go.mongodb.org/mongo-driver/bson/primitive" 9 | ) 10 | 11 | type User struct { 12 | ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` 13 | Username string `bson:"username,omitempty" json:"username,omitempty"` 14 | Email string `bson:"email,omitempty" json:"email,omitempty"` 15 | Password string `bson:"password,omitempty" json:"password,omitempty"` 16 | CreatedAt time.Time `bson:"created_at,omitempty" json:"created_at,omitempty"` 17 | UpdatedAt time.Time `bson:"updated_at,omitempty" json:"updated_at,omitempty"` 18 | } 19 | 20 | // Validate validates the user. 21 | func (u User) ValidateRegister() error { 22 | usernameRegex := regexp.MustCompile(`/^\w+$/i`) 23 | if usernameRegex.MatchString(u.Username) { 24 | return errors.New("Username must be a nonempty alphanumeric string.") 25 | } 26 | 27 | passwordRegex := regexp.MustCompile(`/^\S+$/`) 28 | if passwordRegex.MatchString(u.Password) { 29 | return errors.New("Password must be a nonempty string.") 30 | } 31 | 32 | emailRegex := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`) 33 | if !emailRegex.MatchString(u.Email) { 34 | return errors.New("Email must be a valid email address.") 35 | } 36 | 37 | return nil 38 | } 39 | 40 | func (u User) ValidateLogin() error { 41 | passwordRegex := regexp.MustCompile(`/^\S+$/`) 42 | if passwordRegex.MatchString(u.Password) { 43 | return errors.New("Password must be a nonempty string.") 44 | } 45 | 46 | emailRegex := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`) 47 | if !emailRegex.MatchString(u.Email) { 48 | return errors.New("Email must be a valid email address.") 49 | } 50 | 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /interval/story/api.go: -------------------------------------------------------------------------------- 1 | package story 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gofiber/fiber/v2" 7 | "github.com/tunardev/go-api-boilerplate/interval/models" 8 | "github.com/tunardev/go-api-boilerplate/pkg/errors" 9 | "go.mongodb.org/mongo-driver/bson/primitive" 10 | ) 11 | 12 | type routes struct { 13 | service Service 14 | } 15 | 16 | func (r routes) create(c *fiber.Ctx) error { 17 | // Create a new story. 18 | story := models.Story{} 19 | 20 | // Parse the request body into the story. 21 | err := c.BodyParser(&story) 22 | if err != nil { 23 | return c.Status(http.StatusBadRequest).JSON(errors.BadRequest(err.Error())) 24 | } 25 | 26 | // Validate the story. 27 | if ok := story.Validate(); !ok { 28 | return c.Status(http.StatusBadRequest).JSON(errors.BadRequest("invalid story")) 29 | } 30 | 31 | // Get the user ID from the context. 32 | userID := c.Locals("userID") 33 | if userID == nil { 34 | return c.Status(http.StatusUnauthorized).JSON(errors.Unauthorized("")) 35 | } 36 | 37 | // Convert the user ID to an ObjectID. 38 | objID, err := primitive.ObjectIDFromHex(userID.(string)) 39 | if err != nil { 40 | return c.Status(http.StatusInternalServerError).JSON(errors.InternalServerError(err.Error())) 41 | } 42 | story.UserID = objID 43 | 44 | // Create the story in the database. 45 | story, status, err := r.service.Create(story) 46 | if err != nil { 47 | return c.Status(status).JSON(errors.InternalServerError(err.Error())) 48 | } 49 | 50 | // Return the story. 51 | return c.Status(status).JSON(fiber.Map{ 52 | "data": story, 53 | "status": status, 54 | }) 55 | } 56 | 57 | func (r routes) get(c *fiber.Ctx) error { 58 | // Get the ID from the request parameters. 59 | id := c.Params("id") 60 | 61 | // Get the story from the database. 62 | story, status, err := r.service.Get(id) 63 | if err != nil { 64 | return c.Status(status).JSON(errors.InternalServerError(err.Error())) 65 | } 66 | 67 | // Return the story. 68 | return c.Status(status).JSON(fiber.Map{ 69 | "data": story, 70 | "status": status, 71 | }) 72 | } 73 | 74 | func (r routes) update(c *fiber.Ctx) error { 75 | // Get the ID from the request parameters. 76 | id := c.Params("id") 77 | 78 | // Create a new story. 79 | story := models.Story{} 80 | 81 | // Parse the request body into the story. 82 | err := c.BodyParser(&story) 83 | if err != nil { 84 | return c.Status(http.StatusBadRequest).JSON(errors.BadRequest(err.Error())) 85 | } 86 | 87 | // Validate the story. 88 | if ok := story.Validate(); !ok { 89 | return c.Status(http.StatusBadRequest).JSON(errors.BadRequest("invalid story")) 90 | } 91 | 92 | // Get the user ID from the context. 93 | userID := c.Locals("userID") 94 | if userID == nil { 95 | return c.Status(http.StatusUnauthorized).JSON(errors.Unauthorized("")) 96 | } 97 | 98 | // Update the story in the database. 99 | story, status, err := r.service.Update(id, story) 100 | if err != nil { 101 | return c.Status(status).JSON(errors.InternalServerError(err.Error())) 102 | } 103 | 104 | // Return the story. 105 | return c.Status(status).JSON(fiber.Map{ 106 | "data": story, 107 | "status": status, 108 | }) 109 | } -------------------------------------------------------------------------------- /interval/story/handler.go: -------------------------------------------------------------------------------- 1 | package story 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | "github.com/gofiber/fiber/v2/middleware/session" 6 | "github.com/tunardev/go-api-boilerplate/interval/middleware" 7 | "go.mongodb.org/mongo-driver/mongo" 8 | ) 9 | 10 | // Handler is the handler for the story service. 11 | func Handler(app *fiber.App, collection *mongo.Collection, store *session.Store) { 12 | res := routes{ 13 | service: NewService(NewRepository(collection)), 14 | } 15 | r := app.Group("/story") 16 | 17 | // story routes 18 | r.Get("/:id", res.get) 19 | 20 | // protected routes 21 | r.Use(middleware.IsAuth(store)) 22 | 23 | r.Post("/", res.create) 24 | r.Patch("/:id", res.update) 25 | } -------------------------------------------------------------------------------- /interval/story/repository.go: -------------------------------------------------------------------------------- 1 | package story 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/tunardev/go-api-boilerplate/interval/models" 8 | "go.mongodb.org/mongo-driver/bson/primitive" 9 | "go.mongodb.org/mongo-driver/mongo" 10 | ) 11 | 12 | type Repository interface { 13 | // Create creates a new story. 14 | Create(story models.Story) (models.Story, error) 15 | 16 | // Get gets a story by ID. 17 | Get(id string) (models.Story, error) 18 | 19 | // Delete deletes a story by ID. 20 | Update(id string, story models.Story) (models.Story, error) 21 | } 22 | 23 | type repository struct { 24 | // collection is the MongoDB collection for the story service. 25 | collection *mongo.Collection 26 | } 27 | 28 | // NewRepository creates a new story repository. 29 | func NewRepository(collection *mongo.Collection) Repository { 30 | return repository{collection} 31 | } 32 | 33 | func (r repository) Create(story models.Story) (models.Story, error) { 34 | story.CreatedAt = time.Now() 35 | story.UpdatedAt = time.Now() 36 | 37 | // Insert the story into the database. 38 | res, err := r.collection.InsertOne(context.TODO(), story) 39 | if err != nil { 40 | return models.Story{}, err 41 | } 42 | 43 | // Set the ID of the story to the ID of the inserted document. 44 | story.ID = res.InsertedID.(primitive.ObjectID) 45 | return story, nil 46 | } 47 | 48 | func (r repository) Get(id string) (models.Story, error) { 49 | // Convert the ID to a primitive.ObjectID. 50 | objID, err := primitive.ObjectIDFromHex(id) 51 | if err != nil { 52 | return models.Story{}, err 53 | } 54 | 55 | // Create a new story. 56 | story := models.Story{} 57 | 58 | // Find the story in the database. 59 | err = r.collection.FindOne(context.TODO(), models.Story{ID: objID}).Decode(&story) 60 | if err != nil { 61 | return models.Story{}, err 62 | } 63 | 64 | return story, nil 65 | } 66 | 67 | func (r repository) Update(id string, story models.Story) (models.Story, error) { 68 | // Convert the ID to a primitive.ObjectID. 69 | objID, err := primitive.ObjectIDFromHex(id) 70 | if err != nil { 71 | return models.Story{}, err 72 | } 73 | story.ID = objID 74 | story.UpdatedAt = time.Now() 75 | 76 | // Update the story in the database. 77 | _, err = r.collection.UpdateOne(context.TODO(), models.Story{ID: objID}, story) 78 | if err != nil { 79 | return models.Story{}, err 80 | } 81 | 82 | return story, nil 83 | } 84 | -------------------------------------------------------------------------------- /interval/story/service.go: -------------------------------------------------------------------------------- 1 | package story 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/tunardev/go-api-boilerplate/interval/models" 7 | ) 8 | 9 | type Service interface { 10 | // Create creates a new story. 11 | Create(story models.Story) (models.Story, int, error) 12 | 13 | // Get gets a story by ID. 14 | Get(id string) (models.Story, int, error) 15 | 16 | // Update updates a story by ID. 17 | Update(id string, story models.Story) (models.Story, int, error) 18 | } 19 | 20 | type service struct { 21 | repo Repository 22 | } 23 | 24 | // NewService creates a new story service. 25 | func NewService(repo Repository) Service { 26 | return service{repo} 27 | } 28 | 29 | func (s service) Create(story models.Story) (models.Story, int, error) { 30 | // Create the story in the database. 31 | story, err := s.repo.Create(story) 32 | if err != nil { 33 | return models.Story{}, http.StatusInternalServerError, err 34 | } 35 | 36 | return story, http.StatusCreated, nil 37 | } 38 | 39 | func (s service) Get(id string) (models.Story, int, error) { 40 | // Get the story from the database. 41 | story, err := s.repo.Get(id) 42 | if err != nil { 43 | return models.Story{}, http.StatusInternalServerError, err 44 | } 45 | 46 | return story, http.StatusOK, nil 47 | } 48 | 49 | func (s service) Update(id string, story models.Story) (models.Story, int, error) { 50 | // Update the story in the database. 51 | story, err := s.repo.Update(id, story) 52 | if err != nil { 53 | return models.Story{}, http.StatusInternalServerError, err 54 | } 55 | 56 | return story, http.StatusOK, nil 57 | } 58 | -------------------------------------------------------------------------------- /interval/user/api.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gofiber/fiber/v2" 7 | "github.com/gofiber/fiber/v2/middleware/session" 8 | "github.com/tunardev/go-api-boilerplate/interval/models" 9 | "github.com/tunardev/go-api-boilerplate/pkg/errors" 10 | ) 11 | 12 | type routes struct { 13 | service Service 14 | store *session.Store 15 | } 16 | 17 | func (r routes) register(c *fiber.Ctx) error { 18 | // Create a new user. 19 | user := models.User{} 20 | 21 | // Parse the request body into the user. 22 | if err := c.BodyParser(&user); err != nil { 23 | return c.Status(http.StatusBadRequest).JSON(errors.BadRequest(err.Error())) 24 | } 25 | 26 | // Validate the user. 27 | if err := user.ValidateRegister(); err != nil { 28 | return c.Status(http.StatusBadRequest).JSON(errors.BadRequest(err.Error())) 29 | } 30 | 31 | // Register the user. 32 | user, status, err := r.service.Register(user) 33 | if err != nil { 34 | return c.Status(status).JSON(errors.InternalServerError(err.Error())) 35 | } 36 | 37 | // Get the session. 38 | sess, err := r.store.Get(c) 39 | if err != nil { 40 | return c.Status(http.StatusInternalServerError).JSON(errors.InternalServerError(err.Error())) 41 | } 42 | 43 | // Set the user ID in the session. 44 | sess.Set("userID", user.ID.Hex()) 45 | if err = sess.Save(); err != nil { 46 | return c.Status(http.StatusInternalServerError).JSON(errors.InternalServerError(err.Error())) 47 | } 48 | 49 | // Return the user. 50 | return c.Status(status).JSON(fiber.Map{ 51 | "data": user, 52 | "status": status, 53 | }) 54 | } 55 | 56 | func (r routes) login(c *fiber.Ctx) error { 57 | // Create a new user. 58 | user := models.User{} 59 | 60 | // Parse the request body into the user. 61 | if err := c.BodyParser(&user); err != nil { 62 | return c.Status(http.StatusBadRequest).JSON(errors.BadRequest(err.Error())) 63 | } 64 | 65 | // Validate the user. 66 | if err := user.ValidateLogin(); err != nil { 67 | return c.Status(http.StatusBadRequest).JSON(errors.BadRequest(err.Error())) 68 | } 69 | 70 | // Login the user. 71 | user, status, err := r.service.Login(user) 72 | if err != nil { 73 | return c.Status(status).JSON(errors.InternalServerError(err.Error())) 74 | } 75 | 76 | // Get the session. 77 | sess, err := r.store.Get(c) 78 | if err != nil { 79 | return c.Status(http.StatusInternalServerError).JSON(errors.InternalServerError(err.Error())) 80 | } 81 | 82 | // Set the user ID in the session. 83 | sess.Set("userID", user.ID.Hex()) 84 | if err = sess.Save(); err != nil { 85 | return c.Status(http.StatusInternalServerError).JSON(errors.InternalServerError(err.Error())) 86 | } 87 | 88 | // Return the user. 89 | return c.Status(status).JSON(fiber.Map{ 90 | "data": user, 91 | "status": status, 92 | }) 93 | } 94 | 95 | func (r routes) me(c *fiber.Ctx) error { 96 | // Get the session. 97 | sess, err := r.store.Get(c) 98 | if err != nil { 99 | return c.Status(http.StatusInternalServerError).JSON(errors.InternalServerError(err.Error())) 100 | } 101 | 102 | // Get the user ID from the session. 103 | userID := sess.Get("userID") 104 | if userID == nil { 105 | return c.Status(http.StatusUnauthorized).JSON(errors.Unauthorized("")) 106 | } 107 | 108 | // Get the user from the database. 109 | user, status, err := r.service.Me(userID.(string)) 110 | if err != nil { 111 | return c.Status(status).JSON(errors.InternalServerError(err.Error())) 112 | } 113 | 114 | // Return the user. 115 | return c.Status(http.StatusOK).JSON(fiber.Map{ 116 | "data": user, 117 | "status": http.StatusOK, 118 | }) 119 | } 120 | 121 | func (r routes) logout(c *fiber.Ctx) error { 122 | // Get the session. 123 | sess, err := r.store.Get(c) 124 | if err != nil { 125 | return c.Status(http.StatusInternalServerError).JSON(errors.InternalServerError(err.Error())) 126 | } 127 | 128 | // Delete the user ID from the session. 129 | sess.Delete("userID") 130 | if err = sess.Save(); err != nil { 131 | return c.Status(http.StatusInternalServerError).JSON(errors.InternalServerError(err.Error())) 132 | } 133 | 134 | // Return the user. 135 | return c.Status(http.StatusOK).JSON(fiber.Map{ 136 | "message": "Successfully logged out.", 137 | "status": http.StatusOK, 138 | }) 139 | } 140 | -------------------------------------------------------------------------------- /interval/user/handler.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | "github.com/gofiber/fiber/v2/middleware/session" 6 | "go.mongodb.org/mongo-driver/mongo" 7 | ) 8 | 9 | // Handler is the handler for the user service. 10 | func Handler(app *fiber.App, collection *mongo.Collection, store *session.Store) { 11 | res := routes{ 12 | service: NewService(NewRepository(collection)), 13 | store: store, 14 | } 15 | r := app.Group("/user") 16 | 17 | // user routes 18 | r.Post("/register", res.register) 19 | r.Post("/login", res.login) 20 | r.Get("/me", res.me) 21 | r.Get("/logout", res.logout) 22 | } 23 | -------------------------------------------------------------------------------- /interval/user/repository.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/tunardev/go-api-boilerplate/interval/models" 8 | "go.mongodb.org/mongo-driver/bson/primitive" 9 | "go.mongodb.org/mongo-driver/mongo" 10 | ) 11 | 12 | type Repository interface { 13 | // Create creates a new user. 14 | Create(user models.User) (models.User, error) 15 | 16 | // GetByEmail gets a user by email. 17 | GetByEmail(email string) (models.User, error) 18 | 19 | // GetByUsername gets a user by username. 20 | GetByUsername(username string) (models.User, error) 21 | 22 | // GetByID gets a user by ID. 23 | GetByID(id string) (models.User, error) 24 | } 25 | 26 | type repository struct { 27 | // collection is the MongoDB collection for the user service. 28 | collection *mongo.Collection 29 | } 30 | 31 | // NewRepository creates a new user repository. 32 | func NewRepository(collection *mongo.Collection) Repository { 33 | return repository{collection} 34 | } 35 | 36 | func (r repository) Create(user models.User) (models.User, error) { 37 | user.CreatedAt = time.Now() 38 | user.UpdatedAt = time.Now() 39 | 40 | // Insert the user into the database. 41 | res, err := r.collection.InsertOne(context.TODO(), user) 42 | if err != nil { 43 | return models.User{}, err 44 | } 45 | 46 | // Set the ID of the user to the ID of the inserted document. 47 | user.ID = res.InsertedID.(primitive.ObjectID) 48 | return user, nil 49 | } 50 | 51 | func (r repository) GetByEmail(email string) (models.User, error) { 52 | // Create a new user. 53 | user := models.User{} 54 | 55 | // Find the user in the database. 56 | err := r.collection.FindOne(context.TODO(), models.User{Email: email}).Decode(&user) 57 | if err != nil { 58 | return models.User{}, err 59 | } 60 | 61 | return user, nil 62 | } 63 | 64 | func (r repository) GetByUsername(username string) (models.User, error) { 65 | // Create a new user. 66 | user := models.User{} 67 | 68 | // Find the user in the database. 69 | err := r.collection.FindOne(context.TODO(), models.User{Username: username}).Decode(&user) 70 | if err != nil { 71 | return models.User{}, err 72 | } 73 | 74 | return user, nil 75 | } 76 | 77 | func (r repository) GetByID(id string) (models.User, error) { 78 | // Convert the ID to a primitive.ObjectID. 79 | objID, err := primitive.ObjectIDFromHex(id) 80 | if err != nil { 81 | return models.User{}, err 82 | } 83 | 84 | // Create a new user. 85 | user := models.User{} 86 | 87 | // Find the user in the database. 88 | err = r.collection.FindOne(context.TODO(), models.User{ID: objID}).Decode(&user) 89 | if err != nil { 90 | return models.User{}, err 91 | } 92 | 93 | return user, nil 94 | } -------------------------------------------------------------------------------- /interval/user/service.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | 7 | "github.com/tunardev/go-api-boilerplate/interval/models" 8 | "golang.org/x/crypto/bcrypt" 9 | ) 10 | 11 | type Service interface { 12 | // Register creates a new user. 13 | Register(user models.User) (models.User, int, error) 14 | 15 | // Login login in a user. 16 | Login(user models.User) (models.User, int, error) 17 | 18 | // Me gets the current user. 19 | Me(id string) (models.User, int, error) 20 | } 21 | 22 | type service struct { 23 | repo Repository 24 | } 25 | 26 | // NewService creates a new user service. 27 | func NewService(repo Repository) Service { 28 | return service{repo} 29 | } 30 | 31 | func (s service) Register(user models.User) (models.User, int, error) { 32 | // Check if the username or email already exists. 33 | _, err := s.repo.GetByUsername(user.Username) 34 | if err != nil { 35 | return models.User{}, http.StatusBadRequest, errors.New("username already exists") 36 | } 37 | _, err = s.repo.GetByEmail(user.Email) 38 | if err != nil { 39 | return models.User{}, http.StatusBadRequest, errors.New("email already exists") 40 | } 41 | 42 | // Hash the password. 43 | bytes, err := bcrypt.GenerateFromPassword([]byte(user.Password), 14) 44 | if err != nil { 45 | return models.User{}, http.StatusInternalServerError, err 46 | } 47 | user.Password = string(bytes) 48 | 49 | // Create the user in the database. 50 | user, err = s.repo.Create(user) 51 | if err != nil { 52 | return models.User{}, http.StatusInternalServerError, err 53 | } 54 | 55 | return user, http.StatusCreated, nil 56 | } 57 | 58 | func (s service) Login(user models.User) (models.User, int, error) { 59 | // Get the user from the database. 60 | userData, err := s.repo.GetByEmail(user.Email) 61 | if err != nil { 62 | return models.User{}, http.StatusBadRequest, err 63 | } 64 | 65 | // Compare the password. 66 | err = bcrypt.CompareHashAndPassword([]byte(userData.Password), []byte(user.Password)) 67 | if err != nil { 68 | return models.User{}, http.StatusInternalServerError, err 69 | } 70 | 71 | return user, http.StatusOK, nil 72 | } 73 | 74 | func (s service) Me(id string) (models.User, int, error) { 75 | // Get the user from the database. 76 | user, err := s.repo.GetByID(id) 77 | if err != nil { 78 | return models.User{}, http.StatusBadRequest, err 79 | } 80 | 81 | return user, http.StatusOK, nil 82 | } 83 | -------------------------------------------------------------------------------- /pkg/errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import "net/http" 4 | 5 | // ErrorResponse is the response for an error. 6 | type ErrorResponse struct { 7 | Status int `json:"status"` 8 | Message string `json:"message"` 9 | } 10 | 11 | // Error is required by error interface. 12 | func (e ErrorResponse) Error() string { 13 | return e.Message 14 | } 15 | 16 | // StatusCode returns the HTTP status code for the error. 17 | func (e ErrorResponse) StatusCode() int { 18 | return e.Status 19 | } 20 | 21 | // InternalServerError creates a new error response representing an internal server error (HTTP 500) 22 | func InternalServerError(msg string) ErrorResponse { 23 | if msg == "" { 24 | msg = "Something went wrong. Please try again later." 25 | } 26 | 27 | return ErrorResponse{ 28 | Status: http.StatusInternalServerError, 29 | Message: msg, 30 | } 31 | } 32 | 33 | // BadRequest creates a new error response representing a bad request (HTTP 400) 34 | func BadRequest(msg string) ErrorResponse { 35 | if msg == "" { 36 | msg = "The request was invalid or cannot be otherwise served." 37 | } 38 | 39 | return ErrorResponse{ 40 | Status: http.StatusBadRequest, 41 | Message: msg, 42 | } 43 | } 44 | 45 | // Unauthorized creates a new error response representing an unauthorized request (HTTP 401) 46 | func Unauthorized(msg string) ErrorResponse { 47 | if msg == "" { 48 | msg = "Unauthorized" 49 | } 50 | 51 | return ErrorResponse{ 52 | Status: http.StatusUnauthorized, 53 | Message: msg, 54 | } 55 | } --------------------------------------------------------------------------------