Skip to content

Commit 80cdfd4

Browse files
authored
Merge pull request #275 from gofr-dev/release/v0.3.0
Release/v0.3.0
2 parents 5af23a5 + ee2a07a commit 80cdfd4

40 files changed

+1013
-96
lines changed

Diff for: .github/workflows/docs.yml

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ env:
1515
jobs:
1616
dockerize_stage:
1717
runs-on: ubuntu-latest
18+
if: (github.ref == 'refs/heads/development' || github.ref == 'refs/heads/main' || github.event_name == 'pull_request' )
1819
outputs:
1920
image: ${{ steps.output-image.outputs.image }}
2021
name: 🐳 Dockerize Stage

Diff for: docs/advanced-guide/handling-data-migrations/page.md

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Handling Data Migrations
2+
3+
Gofr supports data migrations for MySQL and Redis which allows to alter the state of a database, be it adding a new column to existing table or modifying the data type of an existing column or adding constraints to an existing table, setting and removing keys etc.
4+
5+
**How Migrations help?**
6+
7+
Suppose you manually edit fragments of your database, and now it's your responsibility to inform other developers to execute them. Additionally, you need to keep track of which changes should be applied to production machines in the next deployment.
8+
9+
GoFr maintains the table called **gofr_migration** which helps in such case. This table tracks which migrations have already been executed and ensures that only migrations that have never been run are executed. This way, you only need to ensure that your migrations are properly in place. ([Learn more](https://cloud.google.com/architecture/database-migration-concepts-principles-part-1))
10+
11+
## Usage
12+
13+
We will create an employee table using migrations.
14+
15+
### Creating Migration Files
16+
17+
It is recommended to maintain a migrations directory in your project root to enhance readability and maintainability.
18+
19+
**Migration file names**
20+
21+
It is recommended that each migration file should be numbered using the unix timestamp when the migration was created, This helps prevent numbering conflicts when working in a team environment.
22+
23+
Create the following file in migrations directory.
24+
25+
**Filename : 1708322067_create_employee_table.go**
26+
```go
27+
package migrations
28+
29+
import "gofr.dev/pkg/gofr/migration"
30+
31+
32+
const createTable = `CREATE TABLE IF NOT EXISTS employee
33+
(
34+
id int not null
35+
primary key,
36+
name varchar(50) not null,
37+
gender varchar(6) not null,
38+
contact_number varchar(10) not null
39+
);`
40+
41+
func createTableEmployee() migration.Migrate {
42+
return migration.Migrate{
43+
UP: func(d migration.Datasource) error {
44+
_, err := d.DB.Exec(createTable)
45+
if err != nil {
46+
return err
47+
}
48+
return nil
49+
},
50+
}
51+
}
52+
```
53+
54+
`migration.Datasource` have the datasources whose migrations are supported which are Redis and MySQL.
55+
All the migrations run in transactions by default.
56+
57+
For MySQL it is highly recommended to use `IF EXISTS` and `IF NOT EXIST` in DDL commands as MySQL implicitly commits these Commands.
58+
59+
**Create a function which returns all the migrations in a map**
60+
61+
**Filename : all.go**
62+
```go
63+
package migrations
64+
65+
import "gofr.dev/pkg/gofr/migration"
66+
67+
func All() map[int64]migration.Migrate {
68+
return map[int64]migration.Migrate{
69+
1708322067: createTableEmployee(),
70+
}
71+
}
72+
```
73+
74+
Migrations will run in ascending order of keys in this map.
75+
76+
### Initialisation from main.go
77+
```go
78+
package main
79+
80+
import (
81+
"errors"
82+
"fmt"
83+
84+
"gofr.dev/examples/using-migrations/migrations"
85+
"gofr.dev/pkg/gofr"
86+
)
87+
88+
func main() {
89+
// Create a new application
90+
a := gofr.New()
91+
92+
// Add migrations to run
93+
a.Migrate(migrations.All())
94+
95+
// Run the application
96+
a.Run()
97+
}
98+
99+
```
100+
101+
When we run the app we will see the following logs for migrations which ran successfully.
102+
103+
```bash
104+
INFO [16:55:46] Migration 1708322067 ran successfully
105+
```
106+
107+
108+
109+

Diff for: docs/navigation.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export const navigation = [
1515
{ title: 'Inter-service HTTP Calls', href: '/docs/advanced-guide/interservice-http-call' },
1616
{ title: 'Publishing Custom Metrics', href: '/docs/advanced-guide/publishing-custom-metrics' },
1717
{ title: 'Remote Log Level Change', href: '/docs/advanced-guide/remote-log-level-change' },
18-
// { title: 'Handling Data Migrations', href: '/docs/advanced-guide/data-migrations' },
18+
{ title: 'Handling Data Migrations', href: '/docs/advanced-guide/handling-data-migrations' },
1919
// { title: 'Dealing with Remote Files', href: '/docs/advanced-guide/remote-files' },
2020
// { title: 'Dynamic Configurations', href: '/docs/advanced-guide/dynamic-configs' },
2121
// { title: 'Supporting OAuth', href: '/docs/advanced-guide/oauth' },

Diff for: examples/sample-cmd/main_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func TestCMDRunWithParams(t *testing.T) {
3434
"command params -name=Vikash",
3535
"command params -name=Vikash",
3636
"command -name=Vikash params",
37+
"command params -name=Vikash -",
3738
}
3839

3940
for i, command := range commands {

Diff for: examples/using-migrations/configs/.env

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
APP_NAME=using-migrations
2+
HTTP_PORT=9000
3+
4+
REDIS_HOST=localhost
5+
REDIS_PORT=2002
6+
7+
DB_HOST=localhost
8+
DB_USER=root
9+
DB_PASSWORD=password
10+
DB_NAME=gofr
11+
DB_PORT=2001
12+
13+
TRACER_HOST=localhost
14+
TRACER_PORT=2005

Diff for: examples/using-migrations/main.go

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"gofr.dev/examples/using-migrations/migrations"
8+
"gofr.dev/pkg/gofr"
9+
)
10+
11+
const (
12+
queryGetEmployee = "SELECT id,name,gender,contact_number,dob from employee where name = ?"
13+
queryInsertEmployee = "INSERT INTO employee (id, name, gender, contact_number,dob) values (?, ?, ?, ?, ?)"
14+
)
15+
16+
func main() {
17+
// Create a new application
18+
a := gofr.New()
19+
20+
// Add migrations to run
21+
a.Migrate(migrations.All())
22+
23+
// Add all the routes
24+
a.GET("/employee", GetHandler)
25+
a.POST("/employee", PostHandler)
26+
27+
// Run the application
28+
a.Run()
29+
}
30+
31+
type Employee struct {
32+
ID int `json:"id"`
33+
Name string `json:"name"`
34+
Gender string `json:"gender"`
35+
Phone int `json:"contact_number"`
36+
DOB string `json:"dob"`
37+
}
38+
39+
// GetHandler handles GET requests for retrieving employee information
40+
func GetHandler(c *gofr.Context) (interface{}, error) {
41+
name := c.Param("name")
42+
if name == "" {
43+
return nil, errors.New("name can't be empty")
44+
}
45+
46+
row := c.DB.QueryRowContext(c, queryGetEmployee, name)
47+
if row.Err() != nil {
48+
return nil, errors.New(fmt.Sprintf("DB Error : %v", row.Err()))
49+
}
50+
51+
var emp Employee
52+
53+
err := row.Scan(&emp.ID, &emp.Name, &emp.Gender, &emp.Phone, &emp.DOB)
54+
if err != nil {
55+
return nil, errors.New(fmt.Sprintf("DB Error : %v", err))
56+
}
57+
58+
return emp, nil
59+
}
60+
61+
// PostHandler handles POST requests for creating new employees
62+
func PostHandler(c *gofr.Context) (interface{}, error) {
63+
var emp Employee
64+
if err := c.Bind(&emp); err != nil {
65+
c.Logger.Errorf("error in binding: %v", err)
66+
return nil, errors.New("invalid body")
67+
}
68+
69+
// Execute the INSERT query
70+
_, err := c.DB.ExecContext(c, queryInsertEmployee, emp.ID, emp.Name, emp.Gender, emp.Phone, emp.DOB)
71+
72+
if err != nil {
73+
return Employee{}, errors.New(fmt.Sprintf("DB Error : %v", err))
74+
}
75+
76+
return fmt.Sprintf("succesfully posted entity : %v", emp.Name), nil
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package migrations
2+
3+
import (
4+
"gofr.dev/pkg/gofr/migration"
5+
)
6+
7+
const createTable = `CREATE TABLE IF NOT EXISTS employee
8+
(
9+
id int not null
10+
primary key,
11+
name varchar(50) not null,
12+
gender varchar(6) not null,
13+
contact_number varchar(10) not null
14+
);`
15+
16+
const employee_date = `INSERT INTO employee (id, name, gender, contact_number) VALUES (1, 'Umang', "M", "0987654321");`
17+
18+
func createTableEmployee() migration.Migrate {
19+
return migration.Migrate{
20+
UP: func(d migration.Datasource) error {
21+
_, err := d.DB.Exec(createTable)
22+
if err != nil {
23+
return err
24+
}
25+
26+
_, err = d.DB.Exec(employee_date)
27+
if err != nil {
28+
return err
29+
}
30+
31+
_, err = d.DB.Exec("alter table employee add dob varchar(11) null;")
32+
if err != nil {
33+
return err
34+
}
35+
36+
return nil
37+
},
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package migrations
2+
3+
import (
4+
"context"
5+
"gofr.dev/pkg/gofr/migration"
6+
)
7+
8+
func addEmployeeInRedis() migration.Migrate {
9+
return migration.Migrate{
10+
UP: func(d migration.Datasource) error {
11+
err := d.Redis.Set(context.Background(), "Umang", "0987654321", 0).Err()
12+
if err != nil {
13+
return err
14+
}
15+
16+
return nil
17+
18+
},
19+
}
20+
}

Diff for: examples/using-migrations/migrations/all.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package migrations
2+
3+
import (
4+
"gofr.dev/pkg/gofr/migration"
5+
)
6+
7+
func All() map[int64]migration.Migrate {
8+
return map[int64]migration.Migrate{
9+
1708322067: createTableEmployee(),
10+
1708322089: addEmployeeInRedis(),
11+
}
12+
}

Diff for: go.mod

+8-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/DATA-DOG/go-sqlmock v1.5.2
77
github.com/alicebob/miniredis/v2 v2.31.1
88
github.com/go-sql-driver/mysql v1.7.1
9+
github.com/gogo/protobuf v1.3.2
910
github.com/gorilla/mux v1.8.1
1011
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
1112
github.com/joho/godotenv v1.5.1
@@ -15,13 +16,13 @@ require (
1516
github.com/stretchr/testify v1.8.4
1617
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.47.0
1718
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0
18-
go.opentelemetry.io/otel v1.22.0
19-
go.opentelemetry.io/otel/exporters/prometheus v0.45.0
20-
go.opentelemetry.io/otel/exporters/zipkin v1.22.0
21-
go.opentelemetry.io/otel/metric v1.22.0
22-
go.opentelemetry.io/otel/sdk v1.22.0
23-
go.opentelemetry.io/otel/sdk/metric v1.22.0
24-
go.opentelemetry.io/otel/trace v1.22.0
19+
go.opentelemetry.io/otel v1.23.1
20+
go.opentelemetry.io/otel/exporters/prometheus v0.45.2
21+
go.opentelemetry.io/otel/exporters/zipkin v1.23.1
22+
go.opentelemetry.io/otel/metric v1.23.1
23+
go.opentelemetry.io/otel/sdk v1.23.1
24+
go.opentelemetry.io/otel/sdk/metric v1.23.1
25+
go.opentelemetry.io/otel/trace v1.23.1
2526
go.uber.org/mock v0.4.0
2627
golang.org/x/term v0.16.0
2728
google.golang.org/grpc v1.61.0

Diff for: go.sum

+15-14
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
4545
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
4646
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
4747
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
48+
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
4849
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
4950
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
5051
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -119,20 +120,20 @@ go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.
119120
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.47.0/go.mod h1:2NonlJyJNVbDK/hCwiLsu5gsD2bVtmIzQ/tGzWq58us=
120121
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08=
121122
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw=
122-
go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y=
123-
go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI=
124-
go.opentelemetry.io/otel/exporters/prometheus v0.45.0 h1:BeIK2KGho0oCWa7LxEGSqfDZbs7Fpv/Viz+FS4P8CXE=
125-
go.opentelemetry.io/otel/exporters/prometheus v0.45.0/go.mod h1:UVJZPLnfDSvHj+eJuZE+E1GjIBD267mEMfAAHJdghWg=
126-
go.opentelemetry.io/otel/exporters/zipkin v1.22.0 h1:18n1VrUfs6uUYg+WgyC4Nl9bsb06gh+swvCVVhfwi7I=
127-
go.opentelemetry.io/otel/exporters/zipkin v1.22.0/go.mod h1:/iI0r/ApELDJC7e+RDbBCxJBPvZ5hV2tVEBfXfgsCRY=
128-
go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg=
129-
go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY=
130-
go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw=
131-
go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc=
132-
go.opentelemetry.io/otel/sdk/metric v1.22.0 h1:ARrRetm1HCVxq0cbnaZQlfwODYJHo3gFL8Z3tSmHBcI=
133-
go.opentelemetry.io/otel/sdk/metric v1.22.0/go.mod h1:KjQGeMIDlBNEOo6HvjhxIec1p/69/kULDcp4gr0oLQQ=
134-
go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0=
135-
go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo=
123+
go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY=
124+
go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA=
125+
go.opentelemetry.io/otel/exporters/prometheus v0.45.2 h1:pe2Jqk1K18As0RCw7J08QhgXNqr+6npx0a5W4IgAFA8=
126+
go.opentelemetry.io/otel/exporters/prometheus v0.45.2/go.mod h1:B38pscHKI6bhFS44FDw0eFU3iqG3ASNIvY+fZgR5sAc=
127+
go.opentelemetry.io/otel/exporters/zipkin v1.23.1 h1:goka4KdsPPpHHQnzp1/XE1wVpk2oQO9RXCOH4MZWSyg=
128+
go.opentelemetry.io/otel/exporters/zipkin v1.23.1/go.mod h1:KXTI1fJdTqRrQlIYgdmF4MnyAbHFWg1z320eOpL53qA=
129+
go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo=
130+
go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI=
131+
go.opentelemetry.io/otel/sdk v1.23.1 h1:O7JmZw0h76if63LQdsBMKQDWNb5oEcOThG9IrxscV+E=
132+
go.opentelemetry.io/otel/sdk v1.23.1/go.mod h1:LzdEVR5am1uKOOwfBWFef2DCi1nu3SA8XQxx2IerWFk=
133+
go.opentelemetry.io/otel/sdk/metric v1.23.1 h1:T9/8WsYg+ZqIpMWwdISVVrlGb/N0Jr1OHjR/alpKwzg=
134+
go.opentelemetry.io/otel/sdk/metric v1.23.1/go.mod h1:8WX6WnNtHCgUruJ4TJ+UssQjMtpxkpX0zveQC8JG/E0=
135+
go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8=
136+
go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI=
136137
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
137138
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
138139
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=

Diff for: pkg/gofr/cmd/request.go

+5
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ func NewRequest(args []string) *Request {
4242
continue
4343
}
4444

45+
if len(arg) == 1 {
46+
continue
47+
}
48+
4549
a := ""
4650
if arg[1] == '-' {
4751
a = arg[2:]
@@ -75,6 +79,7 @@ func (r *Request) PathParam(key string) string {
7579
func (r *Request) Context() context.Context {
7680
return context.Background()
7781
}
82+
7883
func (r *Request) HostName() string {
7984
h, err := os.Hostname()
8085
if err != nil {

0 commit comments

Comments
 (0)