-
Notifications
You must be signed in to change notification settings - Fork 0
/
sample.go
189 lines (150 loc) · 4.97 KB
/
sample.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package main
import (
"context"
"github.com/Masterminds/squirrel"
"github.com/ntbosscher/gobase/auth"
"github.com/ntbosscher/gobase/auth/httpauth"
"github.com/ntbosscher/gobase/env"
"github.com/ntbosscher/gobase/er"
"github.com/ntbosscher/gobase/httpdefaults"
"github.com/ntbosscher/gobase/integrations/s3fs"
"github.com/ntbosscher/gobase/model"
"github.com/ntbosscher/gobase/model/squtil"
"github.com/ntbosscher/gobase/requestip"
"github.com/ntbosscher/gobase/res"
"github.com/ntbosscher/gobase/res/r"
"github.com/pkg/errors"
"log"
"os"
"time"
)
func init() {
// enable error logging
res.SetErrorResponseLogging(os.Stdout)
}
func main() {
rt := r.NewRouter()
// setup database transactions
rt.Use(model.AttachTxHandler("/websocket"))
// make requestip.IP() available
rt.Use(requestip.Middleware())
// setup auth
rt.WithAuth(httpauth.Config{
// .. setup jwt-based authentication
// .. oauth setup (optional)
CredentialChecker: func(ctx context.Context, credential *httpauth.Credential) (*auth.UserInfo, error) {
return nil, errors.New("todo")
},
})
// serve react-app
rt.ReactApp("/", "./react-app/build", "localhost:3000")
// simple route
rt.Add("GET", "/api/products", getProducts)
// restrict to internal users
rt.Add("POST", "/api/product", todo, RoleInternal)
rt.Add("PUT", "/api/product", todo, RoleInternal)
rt.Add("POST", "/api/product/upload", uploadProduct, routeSpecificMiddleware, RoleInternal)
// api versioning (based on X-APIVersion header)
rt.Add("POST", "/api/customer/create", r.Versioned(
r.DefaultVersion(todo),
r.Version("1", todo),
))
// rate limiting
rt.Add("GET", "/api/admin/reports", todo, r.RateLimit(10, 10*time.Second))
// receive a github post-push hook and auto-update ourselves
rt.GithubContinuousDeployment(res.GithubCDInput{
Path: "/api/github-auto-deploy",
Secret: env.Require("GITHUB_DEPLOY_KEY"),
PostPullScript: "./rebuild-and-migrate.sh",
})
server := httpdefaults.Server("8080", rt)
log.Println("Serving on " + server.Addr)
log.Fatal(server.ListenAndServe())
}
var routeSpecificMiddleware = r.Middleware(func(router *r.Router, method string, path string, handler res.HandlerFunc2) res.HandlerFunc2 {
return func(rq *res.Request) res.Responder {
if rq.Request().Header.Get("X-Source") == "mobile" {
return res.Error(errors.New("mobile not supported on this route"))
}
return handler(rq)
}
})
const (
RoleUser auth.TRole = 0x1 << iota
RoleSupport auth.TRole = 0x1 << iota
RoleSuperUser auth.TRole = 0x1 << iota
)
const (
RoleInternal = RoleSuperUser | RoleSupport
)
func getProducts(rq *res.Request) res.Responder {
return res.Todo()
}
func uploadProduct(rq *res.Request) res.Responder {
file := rq.MultipartFile("file")
err := s3fs.Upload(rq.Context(), []*s3fs.UploadInput{{
FileName: file.Filename,
Key: "product-file",
FileHeader: file,
}})
if err != nil {
return res.Error(err)
}
return res.Ok()
}
type User struct {
ID int
FirstName string
Email string
CreatedBy int
Company int `json:"-"`
}
func createUserHandler(rq *res.Request) res.Responder {
// define the request structure, could have used
// the User struct here instead.
input := &struct {
FirstName string
Email string
}{}
// parse request body, if fails will return a 400 with error details
rq.MustParseJSON(input)
// use the auth-context to get which company/tenant (for multi-tenant systems)
company := auth.Company(rq.Context())
currentUser := auth.User(rq.Context())
// create user using if/err flow
id := 0
err := model.QueryRowContext(rq.Context(), `insert into user
(first_name, email, company, created_by)
values ($1, $2, $3, $4) returning id`,
input.FirstName, input.Email, company, currentUser).Scan(&id)
if model.IsDuplicateKeyError(err) {
return res.AppError("That email is already in the system")
}
// fetch user
// db transaction flows from model.AttachTxHandler through rq.Context() and
// will be auto committed if we return a non-error response
customer := &User{}
model.MustGetContext(rq.Context(), customer, `select * from user where id = $1`, id)
// returns json with http-status 200 -> { id: 1, firstName: "", email: "", createdBy: 1 }
return res.Ok(customer)
}
func todo(rq *res.Request) res.Responder {
return res.Todo()
}
type Customer struct{}
func getCustomers(rq *res.Request) res.Responder {
// parse 'limit' query parameter
limit := rq.GetQueryInt("limit")
customer := &Customer{}
qr := model.Builder.Select("*").From("customer").Where(squirrel.Eq{
"company": auth.Company(rq.Context()),
}).Limit(uint64(limit))
// db transaction flows from model.AttachTxHandler through rq.Context() and
// will be auto committed if we return a non-error response
//
// get current user's company id from context
err := squtil.GetContext(rq.Context(), customer, qr)
er.Check(err) // return http 500 with json encoded {error: "string"} value
// return http 200 with customer json encoded with camelCase keys
return res.Ok(customer)
}