Skip to content

Commit f9d1a9c

Browse files
add code
1 parent a2a0f05 commit f9d1a9c

File tree

18 files changed

+1406
-2
lines changed

18 files changed

+1406
-2
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,8 @@
1313

1414
# Dependency directories (remove the comment below to include it)
1515
# vendor/
16+
17+
# version control
18+
.vscode
19+
.idea
20+
*.SVN

README.md

Lines changed: 210 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,210 @@
1-
# go-cassandra-rest-api
2-
go-cassandra-rest-api
1+
# go-sql-rest-api
2+
3+
## How to run
4+
#### Clone the repository
5+
```shell
6+
git clone https://github.com/go-tutorials/go-cassandra-rest-api.git
7+
cd go-cassandra-rest-api
8+
```
9+
10+
#### To run the application
11+
```shell
12+
go run main.go
13+
```
14+
15+
## API Design
16+
### Common HTTP methods
17+
- GET: retrieve a representation of the resource
18+
- POST: create a new resource
19+
- PUT: update the resource
20+
- PATCH: perform a partial update of a resource
21+
- DELETE: delete a resource
22+
23+
## API design for health check
24+
To check if the service is available.
25+
#### *Request:* GET /health
26+
#### *Response:*
27+
```json
28+
{
29+
"status": "UP",
30+
"details": {
31+
"sql": {
32+
"status": "UP"
33+
}
34+
}
35+
}
36+
```
37+
38+
## API design for users
39+
#### *Resource:* users
40+
41+
### Get all users
42+
#### *Request:* GET /users
43+
#### *Response:*
44+
```json
45+
[
46+
{
47+
"id": "spiderman",
48+
"username": "peter.parker",
49+
"email": "peter.parker@gmail.com",
50+
"phone": "0987654321",
51+
"dateOfBirth": "1962-08-25T16:59:59.999Z"
52+
},
53+
{
54+
"id": "wolverine",
55+
"username": "james.howlett",
56+
"email": "james.howlett@gmail.com",
57+
"phone": "0987654321",
58+
"dateOfBirth": "1974-11-16T16:59:59.999Z"
59+
}
60+
]
61+
```
62+
63+
### Get one user by id
64+
#### *Request:* GET /users/:id
65+
```shell
66+
GET /users/wolverine
67+
```
68+
#### *Response:*
69+
```json
70+
{
71+
"id": "wolverine",
72+
"username": "james.howlett",
73+
"email": "james.howlett@gmail.com",
74+
"phone": "0987654321",
75+
"dateOfBirth": "1974-11-16T16:59:59.999Z"
76+
}
77+
```
78+
79+
### Create a new user
80+
#### *Request:* POST /users
81+
```json
82+
{
83+
"id": "wolverine",
84+
"username": "james.howlett",
85+
"email": "james.howlett@gmail.com",
86+
"phone": "0987654321",
87+
"dateOfBirth": "1974-11-16T16:59:59.999Z"
88+
}
89+
```
90+
#### *Response:* 1: success, 0: duplicate key, -1: error
91+
```json
92+
1
93+
```
94+
95+
### Update one user by id
96+
#### *Request:* PUT /users/:id
97+
```shell
98+
PUT /users/wolverine
99+
```
100+
```json
101+
{
102+
"username": "james.howlett",
103+
"email": "james.howlett@gmail.com",
104+
"phone": "0987654321",
105+
"dateOfBirth": "1974-11-16T16:59:59.999Z"
106+
}
107+
```
108+
#### *Response:* 1: success, 0: not found, -1: error
109+
```json
110+
1
111+
```
112+
113+
### Delete a new user by id
114+
#### *Request:* DELETE /users/:id
115+
```shell
116+
DELETE /users/wolverine
117+
```
118+
#### *Response:* 1: success, 0: not found, -1: error
119+
```json
120+
1
121+
```
122+
123+
## Common libraries
124+
- [core-go/health](https://github.com/core-go/health): include HealthHandler, HealthChecker, SqlHealthChecker
125+
- [core-go/config](https://github.com/core-go/config): to load the config file, and merge with other environments (SIT, UAT, ENV)
126+
- [core-go/log](https://github.com/core-go/log): log and log middleware
127+
128+
### core-go/health
129+
To check if the service is available, refer to [core-go/health](https://github.com/core-go/health)
130+
#### *Request:* GET /health
131+
#### *Response:*
132+
```json
133+
{
134+
"status": "UP",
135+
"details": {
136+
"sql": {
137+
"status": "UP"
138+
}
139+
}
140+
}
141+
```
142+
To create health checker, and health handler
143+
```go
144+
cqlChecker := s.NewSqlHealthChecker(db)
145+
healthHandler := health.NewHealthHandler(cqlChecker)
146+
```
147+
148+
To handler routing
149+
```go
150+
r := mux.NewRouter()
151+
r.HandleFunc("/health", healthHandler.Check).Methods("GET")
152+
```
153+
154+
### core-go/config
155+
To load the config from "config.yml", in "configs" folder
156+
```go
157+
package main
158+
159+
import "github.com/core-go/config"
160+
161+
type Root struct {
162+
Server sv.ServerConfig `mapstructure:"server"`
163+
Cql Cassandra `mapstructure:"cassandra"`
164+
Log log.Config `mapstructure:"log"`
165+
MiddleWare mid.LogConfig `mapstructure:"middleware"`
166+
}
167+
168+
type Cassandra struct {
169+
PublicIp string `mapstructure:"public_ip"`
170+
UserName string `mapstructure:"user_name"`
171+
Password string `mapstructure:"password"`
172+
}
173+
174+
175+
func main() {
176+
var conf Root
177+
err := config.Load(&conf, "configs/config")
178+
if err != nil {
179+
panic(err)
180+
}
181+
}
182+
```
183+
184+
### core-go/log *&* core-go/middleware
185+
```go
186+
import (
187+
"github.com/core-go/config"
188+
"github.com/core-go/log"
189+
m "github.com/core-go/middleware"
190+
"github.com/gorilla/mux"
191+
)
192+
193+
func main() {
194+
var conf app.Root
195+
config.Load(&conf, "configs/config")
196+
197+
r := mux.NewRouter()
198+
199+
log.Initialize(conf.Log)
200+
r.Use(m.BuildContext)
201+
logger := m.NewStructuredLogger()
202+
r.Use(m.Logger(conf.MiddleWare, log.InfoFields, logger))
203+
r.Use(m.Recover(log.ErrorMsg))
204+
}
205+
```
206+
To configure to ignore the health check, use "skips":
207+
```yaml
208+
middleware:
209+
skips: /health
210+
```

configs/config.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
server:
2+
name: go-cql-rest-api
3+
port: 8080
4+
5+
cassandra:
6+
public_ip: localhost
7+
user_name: cassandra
8+
password: cassandra
9+
10+
log:
11+
level: info
12+
map:
13+
time: "@timestamp"
14+
msg: message
15+
16+
middleware:
17+
log: true
18+
skips: /health
19+
request: request
20+
response: response
21+
size: size

cql/database.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package cql
2+
3+
import (
4+
"errors"
5+
s "github.com/core-go/sql"
6+
"github.com/gocql/gocql"
7+
"reflect"
8+
)
9+
10+
func BuildParam(i int) string {
11+
return "?"
12+
}
13+
14+
func Patch(cluster *gocql.ClusterConfig, table string, model map[string]interface{}, modelType reflect.Type) (int64, error) {
15+
session, err := cluster.CreateSession()
16+
if err != nil{
17+
return 0, err
18+
}
19+
idcolumNames, idJsonName := s.FindNames(modelType)
20+
columNames := s.FindJsonName(modelType)
21+
query, value := s.BuildPatch(table, model, columNames, idJsonName, idcolumNames, BuildParam)
22+
if query == "" {
23+
return 0, errors.New("fail to build query")
24+
}
25+
err = session.Query(query, value...).Exec()
26+
if err != nil {
27+
return -1, err
28+
}
29+
return 1, nil
30+
}

cql/health_checker.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package cql
2+
3+
import (
4+
"context"
5+
"errors"
6+
"github.com/gocql/gocql"
7+
"time"
8+
)
9+
10+
type HealthChecker struct {
11+
Cluster *gocql.ClusterConfig
12+
name string
13+
timeout time.Duration
14+
}
15+
16+
func NewSqlHealthChecker(cluster *gocql.ClusterConfig, name string, timeouts ...time.Duration) *HealthChecker {
17+
var timeout time.Duration
18+
if len(timeouts) >= 1 {
19+
timeout = timeouts[0]
20+
} else {
21+
timeout = 4 * time.Second
22+
}
23+
return &HealthChecker{Cluster: cluster, name: name, timeout: timeout}
24+
}
25+
func NewHealthChecker(cluster *gocql.ClusterConfig, options ...string) *HealthChecker {
26+
var name string
27+
if len(options) >= 1 && len(options[0]) > 0 {
28+
name = options[0]
29+
} else {
30+
name = "cql"
31+
}
32+
return NewSqlHealthChecker(cluster, name, 4*time.Second)
33+
}
34+
35+
func (s *HealthChecker) Name() string {
36+
return s.name
37+
}
38+
39+
func (s *HealthChecker) Check(ctx context.Context) (map[string]interface{}, error) {
40+
res := make(map[string]interface{}, 0)
41+
if s.timeout > 0 {
42+
ctx, _ = context.WithTimeout(ctx, s.timeout)
43+
}
44+
45+
checkerChan := make(chan error)
46+
go func() {
47+
_, err := s.Cluster.CreateSession()
48+
checkerChan <- err
49+
}()
50+
select {
51+
case err := <-checkerChan:
52+
if err != nil {
53+
return res, err
54+
}
55+
return res, err
56+
case <-ctx.Done():
57+
return res, errors.New("connection timout")
58+
}
59+
}
60+
61+
func (s *HealthChecker) Build(ctx context.Context, data map[string]interface{}, err error) map[string]interface{} {
62+
if err == nil {
63+
return data
64+
}
65+
if data == nil {
66+
data = make(map[string]interface{}, 0)
67+
}
68+
data["error"] = err.Error()
69+
return data
70+
}
71+

data/data.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[
2+
{"id": "ironman", "username": "tony.stark", "email": "tony.stark@gmail.com", "phone": "0987654321", "dateOfBirth": "1963-03-25T00:00:00+07:00"},
3+
{"id": "spiderman", "username": "peter.parker", "email": "peter.parker@gmail.com", "phone": "0987654321", "dateOfBirth": "1962-08-25T00:00:00+07:00"},
4+
{"id": "wolverine", "username": "james.howlett", "email": "james.howlett@gmail.com", "phone": "0987654321", "dateOfBirth": "1974-11-16T00:00:00+07:00"}
5+
]

data/data.sql

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
create table if not exists users (
2+
id varchar,
3+
username varchar,
4+
email varchar,
5+
phone varchar,
6+
date_of_birth date,
7+
primary key (id)
8+
);
9+
10+
insert into users (id, username, email, phone, date_of_birth) values ('ironman', 'tony.stark', 'tony.stark@gmail.com', '0987654321', '1963-03-25');
11+
insert into users (id, username, email, phone, date_of_birth) values ('spiderman', 'peter.parker', 'peter.parker@gmail.com', '0987654321', '1962-08-25');
12+
insert into users (id, username, email, phone, date_of_birth) values ('wolverine', 'james.howlett', 'james.howlett@gmail.com', '0987654321', '1974-11-16');

0 commit comments

Comments
 (0)