Skip to content

Commit 2998ead

Browse files
Merge pull request #75 from samueldasilvadev/multi-tenant
chore(multi-tenant): Creating multi tenant based on database
2 parents 5515565 + d2c5871 commit 2998ead

File tree

26 files changed

+376
-145
lines changed

26 files changed

+376
-145
lines changed

cmd/cli/migrator/migrator.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import (
1111
)
1212

1313
type Migrator struct {
14-
dsn string
15-
dsnTest string
14+
dsn string
15+
dsnTest string
16+
database string
1617
}
1718

1819
func NewMigrator() *Migrator {
@@ -43,13 +44,17 @@ func (m *Migrator) DeclareCommands(cmd *cobra.Command) {
4344
)
4445
}
4546

46-
func (m *Migrator) Migrate(_ *cobra.Command, _ []string) {
47-
migratorInstance := migrator.NewMigrator(m.dsn, m.dsnTest)
48-
migratorInstance.MigrateAllDomains()
47+
func (m *Migrator) Migrate(_ *cobra.Command, args []string) {
48+
tenant := ""
49+
if len(args) > 0 {
50+
tenant = args[0]
51+
}
52+
migratorInstance := migrator.NewMigrator(m.dsn, m.dsnTest, m.database)
53+
migratorInstance.MigrateAllDomains(tenant)
4954
}
5055

5156
func (m *Migrator) Inspect(_ *cobra.Command, _ []string) {
52-
migratorInstance := migrator.NewMigrator(m.dsn, m.dsnTest)
57+
migratorInstance := migrator.NewMigrator(m.dsn, m.dsnTest, m.database)
5358
migratorInstance.Inspect()
5459
}
5560

@@ -58,7 +63,7 @@ func (m *Migrator) Generate(_ *cobra.Command, args []string) {
5863
if len(args) > 1 {
5964
schema = args[1]
6065
}
61-
migratorInstance := migrator.NewMigrator(m.dsn, m.dsnTest)
66+
migratorInstance := migrator.NewMigrator(m.dsn, m.dsnTest, m.database)
6267
migratorInstance.Generate(schema)
6368
}
6469

@@ -69,18 +74,19 @@ func (m *Migrator) BootMigrator(_ *cobra.Command, _ []string) {
6974
panic(err)
7075
}
7176

72-
dsn := "%s:%s@%s:%s/%s"
77+
m.database = conf.ReadConfig("DB_DATABASE")
78+
dsn := "%s:%s@%s:%s"
7379
m.dsn = fmt.Sprintf(
7480
dsn,
7581
url.QueryEscape(conf.ReadConfig("DB_USER")),
7682
url.QueryEscape(conf.ReadConfig("DB_PASS")),
7783
url.QueryEscape(conf.ReadConfig("DB_URL")),
7884
conf.ReadConfig("DB_PORT"),
79-
conf.ReadConfig("DB_DATABASE"),
8085
)
8186

87+
testDsn := "%s:%s@%s:%s/%s"
8288
m.dsnTest = fmt.Sprintf(
83-
dsn,
89+
testDsn,
8490
conf.ReadConfig("DB_USER"),
8591
conf.ReadConfig("DB_PASS"),
8692
conf.ReadConfig("DB_URL"),

cmd/http/handlers/dummy.go

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package handlers
22

33
import (
4+
requestContext "go-skeleton/internal/application/context"
45
"go-skeleton/internal/application/domain/dummy"
56
"go-skeleton/internal/application/providers/filters"
67
"go-skeleton/internal/application/providers/pagination"
@@ -43,6 +44,7 @@ func NewDummyHandlers(reg *registry.Registry) *DummyHandlers {
4344
// @Accept json
4445
// @Produce json
4546
// @Param dummy_id path string true "Dummy ID"
47+
// @Param Tenant header string true "tenant name"
4648
// @Success 200 {object} dummyGet.Response
4749
// @Failure 400 {object} services.Error
4850
// @Failure 404 {object} services.Error
@@ -55,9 +57,12 @@ func (hs *DummyHandlers) HandleGetDummy(context echo.Context) error {
5557
if errors := context.Bind(data); errors != nil {
5658
return context.JSON(422, errors)
5759
}
58-
60+
tenant := context.Get("tenant").(string)
61+
request := dummyGet.NewRequest(data)
62+
ctx := requestContext.NewPrepareContext(tenant)
63+
ctx.SetContext(request.Domain)
5964
s.Execute(
60-
dummyGet.NewRequest(data),
65+
request,
6166
)
6267

6368
response, err := s.GetResponse()
@@ -73,6 +78,7 @@ func (hs *DummyHandlers) HandleGetDummy(context echo.Context) error {
7378
// @Accept json
7479
// @Produce json
7580
// @Param request body dummyCreate.Data true "body model"
81+
// @Param Tenant header string true "tenant name"
7682
// @Success 200 {object} dummyCreate.Response
7783
// @Failure 400 {object} services.Error
7884
// @Failure 404 {object} services.Error
@@ -86,8 +92,12 @@ func (hs *DummyHandlers) HandleCreateDummy(context echo.Context) error {
8692
return context.JSON(http.StatusBadRequest, errors)
8793
}
8894

95+
tenant := context.Get("tenant").(string)
96+
request := dummyCreate.NewRequest(data, hs.validator)
97+
ctx := requestContext.NewPrepareContext(tenant)
98+
ctx.SetContext(request.Domain)
8999
s.Execute(
90-
dummyCreate.NewRequest(data, hs.validator),
100+
request,
91101
)
92102

93103
response, err := s.GetResponse()
@@ -104,6 +114,7 @@ func (hs *DummyHandlers) HandleCreateDummy(context echo.Context) error {
104114
// @Produce json
105115
// @Param dummy_id path string true "Dummy ID"
106116
// @Param request body dummyEdit.Data true "body model"
117+
// @Param Tenant header string true "tenant name"
107118
// @Success 200 {object} dummyEdit.Response
108119
// @Failure 400 {object} services.Error
109120
// @Failure 404 {object} services.Error
@@ -118,8 +129,12 @@ func (hs *DummyHandlers) HandleEditDummy(context echo.Context) error {
118129
return context.JSON(http.StatusBadRequest, errors)
119130
}
120131

132+
tenant := context.Get("tenant").(string)
133+
request := dummyEdit.NewRequest(id, data, hs.validator)
134+
ctx := requestContext.NewPrepareContext(tenant)
135+
ctx.SetContext(request.Domain)
121136
s.Execute(
122-
dummyEdit.NewRequest(id, data, hs.validator),
137+
request,
123138
)
124139

125140
response, err := s.GetResponse()
@@ -137,6 +152,7 @@ func (hs *DummyHandlers) HandleEditDummy(context echo.Context) error {
137152
// @Param page query int true "valid int"
138153
// @Param name query string false "value example: eql|lik,value"
139154
// @Param email query string false "value example: lik,value"
155+
// @Param Tenant header string true "tenant name"
140156
// @Success 200 {object} dummyList.Response
141157
// @Failure 400 {object} services.Error
142158
// @Failure 404 {object} services.Error
@@ -162,8 +178,12 @@ func (hs *DummyHandlers) HandleListDummy(context echo.Context) error {
162178

163179
f := filters.NewFilters()
164180

181+
tenant := context.Get("tenant").(string)
182+
request := dummyList.NewRequest(data, f)
183+
ctx := requestContext.NewPrepareContext(tenant)
184+
ctx.SetContext(request.Domain)
165185
s.Execute(
166-
dummyList.NewRequest(data, *f),
186+
request,
167187
)
168188

169189
response, err := s.GetResponse()
@@ -179,6 +199,7 @@ func (hs *DummyHandlers) HandleListDummy(context echo.Context) error {
179199
// @Accept json
180200
// @Produce json
181201
// @Param dummy_id path string true "Dummy ID"
202+
// @Param Tenant header string true "tenant name"
182203
// @Success 200 {object} dummyDelete.Response
183204
// @Failure 400 {object} services.Error
184205
// @Failure 404 {object} services.Error
@@ -191,9 +212,12 @@ func (hs *DummyHandlers) HandleDeleteDummy(context echo.Context) error {
191212
if errors := context.Bind(data); errors != nil {
192213
return context.JSON(http.StatusBadRequest, errors)
193214
}
194-
215+
tenant := context.Get("tenant").(string)
216+
request := dummyDelete.NewRequest(data)
217+
ctx := requestContext.NewPrepareContext(tenant)
218+
ctx.SetContext(request.Domain)
195219
s.Execute(
196-
dummyDelete.NewRequest(data),
220+
request,
197221
)
198222

199223
response, err := s.GetResponse()
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package middlewares
2+
3+
import "github.com/labstack/echo/v4"
4+
5+
func SetTenant(next echo.HandlerFunc) echo.HandlerFunc {
6+
return func(c echo.Context) error {
7+
c.Set("tenant", c.Request().Header.Get("Tenant"))
8+
// tenant validation Ex: get token and verify data or get tenant from session
9+
return next(c)
10+
}
11+
}

cmd/http/server/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package server
33
import (
44
"fmt"
55
echoSwagger "github.com/swaggo/echo-swagger"
6+
"go-skeleton/cmd/http/middlewares"
67
"go-skeleton/cmd/http/routes"
78
"go-skeleton/docs"
89
"go-skeleton/pkg/config"
@@ -42,6 +43,7 @@ func (hs *Server) Start() {
4243
}
4344

4445
server.Use(middleware.Recover())
46+
server.Use(middlewares.SetTenant)
4547

4648
public := server.Group("")
4749
private := server.Group("")

compose.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@ services:
2626
retries: 2
2727
environment:
2828
MYSQL_ROOT_PASSWORD: root
29-
MYSQL_DATABASE: skeleton
29+
MYSQL_DATABASE: test
3030
ports:
3131
- "3307:3306"
3232
volumes:
33-
- ./storage/mysql:/var/lib/mysql
33+
- storage:/var/lib/mysql
34+
35+
volumes:
36+
storage:
3437

3538
networks:
3639
default:

docs/docs.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ const docTemplate = `{
4646
"description": "value example: lik,value",
4747
"name": "email",
4848
"in": "query"
49+
},
50+
{
51+
"type": "string",
52+
"description": "tenant name",
53+
"name": "Tenant",
54+
"in": "header",
55+
"required": true
4956
}
5057
],
5158
"responses": {
@@ -95,6 +102,13 @@ const docTemplate = `{
95102
"schema": {
96103
"$ref": "#/definitions/go-skeleton_internal_application_services_dummy_CREATE.Data"
97104
}
105+
},
106+
{
107+
"type": "string",
108+
"description": "tenant name",
109+
"name": "Tenant",
110+
"in": "header",
111+
"required": true
98112
}
99113
],
100114
"responses": {
@@ -144,6 +158,13 @@ const docTemplate = `{
144158
"name": "dummy_id",
145159
"in": "path",
146160
"required": true
161+
},
162+
{
163+
"type": "string",
164+
"description": "tenant name",
165+
"name": "Tenant",
166+
"in": "header",
167+
"required": true
147168
}
148169
],
149170
"responses": {
@@ -200,6 +221,13 @@ const docTemplate = `{
200221
"schema": {
201222
"$ref": "#/definitions/go-skeleton_internal_application_services_dummy_EDIT.Data"
202223
}
224+
},
225+
{
226+
"type": "string",
227+
"description": "tenant name",
228+
"name": "Tenant",
229+
"in": "header",
230+
"required": true
203231
}
204232
],
205233
"responses": {
@@ -247,6 +275,13 @@ const docTemplate = `{
247275
"name": "dummy_id",
248276
"in": "path",
249277
"required": true
278+
},
279+
{
280+
"type": "string",
281+
"description": "tenant name",
282+
"name": "Tenant",
283+
"in": "header",
284+
"required": true
250285
}
251286
],
252287
"responses": {

docs/swagger.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@
3838
"description": "value example: lik,value",
3939
"name": "email",
4040
"in": "query"
41+
},
42+
{
43+
"type": "string",
44+
"description": "tenant name",
45+
"name": "Tenant",
46+
"in": "header",
47+
"required": true
4148
}
4249
],
4350
"responses": {
@@ -87,6 +94,13 @@
8794
"schema": {
8895
"$ref": "#/definitions/go-skeleton_internal_application_services_dummy_CREATE.Data"
8996
}
97+
},
98+
{
99+
"type": "string",
100+
"description": "tenant name",
101+
"name": "Tenant",
102+
"in": "header",
103+
"required": true
90104
}
91105
],
92106
"responses": {
@@ -136,6 +150,13 @@
136150
"name": "dummy_id",
137151
"in": "path",
138152
"required": true
153+
},
154+
{
155+
"type": "string",
156+
"description": "tenant name",
157+
"name": "Tenant",
158+
"in": "header",
159+
"required": true
139160
}
140161
],
141162
"responses": {
@@ -192,6 +213,13 @@
192213
"schema": {
193214
"$ref": "#/definitions/go-skeleton_internal_application_services_dummy_EDIT.Data"
194215
}
216+
},
217+
{
218+
"type": "string",
219+
"description": "tenant name",
220+
"name": "Tenant",
221+
"in": "header",
222+
"required": true
195223
}
196224
],
197225
"responses": {
@@ -239,6 +267,13 @@
239267
"name": "dummy_id",
240268
"in": "path",
241269
"required": true
270+
},
271+
{
272+
"type": "string",
273+
"description": "tenant name",
274+
"name": "Tenant",
275+
"in": "header",
276+
"required": true
242277
}
243278
],
244279
"responses": {

0 commit comments

Comments
 (0)