Skip to content

Commit

Permalink
feat: add customizable ORM support for apps and models
Browse files Browse the repository at this point in the history
This commit introduces the ability to specify a custom ORM
integrator for each registered app and model within the admin panel.
By allowing an ORM to be passed as an argument during the registration
of apps and models, developers can now override the default ORM
integrator, providing increased flexibility and customization options.
This enhancement ensures that different parts of the application can
operate with distinct data management strategies if needed.
  • Loading branch information
infuzu-yidisprei committed Oct 4, 2024
1 parent 6414d7b commit d6820f2
Show file tree
Hide file tree
Showing 9 changed files with 69 additions and 47 deletions.
12 changes: 6 additions & 6 deletions example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,31 +63,31 @@ func main() {
log.Fatal(err)
}

testApp1, err := panel.RegisterApp("TestApp1", "Test App 1")
testApp1, err := panel.RegisterApp("TestApp1", "Test App 1", nil)
if err != nil {
log.Fatal(err)
}

_, err = testApp1.RegisterModel(&TestModel1{})
_, err = testApp1.RegisterModel(&TestModel1{}, nil)
if err != nil {
log.Fatal(err)
}
_, err = testApp1.RegisterModel(&TestModel2{})
_, err = testApp1.RegisterModel(&TestModel2{}, nil)
if err != nil {
log.Fatal(err)
}

testApp2, err := panel.RegisterApp("TestApp2", "Test App 2")
testApp2, err := panel.RegisterApp("TestApp2", "Test App 2", nil)
if err != nil {
log.Fatal(err)
}

_, err = testApp2.RegisterModel(&TestModel3{})
_, err = testApp2.RegisterModel(&TestModel3{}, nil)
if err != nil {
log.Fatal(err)
}

_, err = testApp2.RegisterModel(&TestModel4{})
_, err = testApp2.RegisterModel(&TestModel4{}, nil)
if err != nil {
log.Fatal(err)
}
Expand Down
11 changes: 10 additions & 1 deletion internal/adminpanel/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,17 @@ type App struct {
Models map[string]*Model
ModelsSlice []*Model
Panel *AdminPanel
ORM ORMIntegrator
}

func (a *App) RegisterModel(model interface{}) (*Model, error) {
func (a *App) GetORM() ORMIntegrator {
if a.ORM != nil {
return a.ORM
}
return a.Panel.GetORM()
}

func (a *App) RegisterModel(model interface{}, orm ORMIntegrator) (*Model, error) {
modelType := reflect.TypeOf(model)

if modelType.Kind() != reflect.Ptr {
Expand Down Expand Up @@ -366,6 +374,7 @@ func (a *App) RegisterModel(model interface{}) (*Model, error) {
Fields: fieldConfigs,
PrimaryKeyGetter: primaryKeyGetter,
PrimaryKeyType: primaryKeyType,
ORM: orm,
}
a.Panel.Web.HandleRoute("GET", a.Panel.Config.GetPrefix()+modelInstance.GetLink(), modelInstance.GetViewHandler())
a.Panel.Web.HandleRoute("GET", a.Panel.Config.GetPrefix()+modelInstance.GetLink()+"/:id/view", modelInstance.GetInstanceViewHandler())
Expand Down
40 changes: 20 additions & 20 deletions internal/adminpanel/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestRegisterModel(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
testApp, err := panel.RegisterApp("TestApp", "Test App")
testApp, err := panel.RegisterApp("TestApp", "Test App", nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
Expand All @@ -60,7 +60,7 @@ func TestRegisterModel(t *testing.T) {
testModel := &struct {
ID uint `gorm:"primarykey"`
}{}
model, err := testApp.RegisterModel(testModel)
model, err := testApp.RegisterModel(testModel, nil)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
Expand All @@ -78,19 +78,19 @@ func TestRegisterModel(t *testing.T) {
t.Run("DuplicateModel", func(t *testing.T) {
testApp := createTestApp()
testModel := &TestModel1{}
_, err := testApp.RegisterModel(testModel)
_, err := testApp.RegisterModel(testModel, nil)
if err != nil {
t.Fatalf("expected no error on first registration, got %v", err)
}
_, err = testApp.RegisterModel(testModel)
_, err = testApp.RegisterModel(testModel, nil)
if err == nil {
t.Error("expected an error when registering the same model twice")
}
})

t.Run("NonPointerModel", func(t *testing.T) {
testApp := createTestApp()
_, err := testApp.RegisterModel(NonPointerModel{})
_, err := testApp.RegisterModel(NonPointerModel{}, nil)
if err == nil {
t.Error("expected an error when registering a non-pointer model")
}
Expand All @@ -99,15 +99,15 @@ func TestRegisterModel(t *testing.T) {
t.Run("PointerToNonStruct", func(t *testing.T) {
testApp := createTestApp()
var testString *string
_, err := testApp.RegisterModel(testString)
_, err := testApp.RegisterModel(testString, nil)
if err == nil {
t.Error("expected an error when registering a pointer to a non-struct type")
}
})

t.Run("InvalidPrimaryKeyGetter", func(t *testing.T) {
testApp := createTestApp()
_, err := testApp.RegisterModel(&InvalidPointerModel{})
_, err := testApp.RegisterModel(&InvalidPointerModel{}, nil)
if err == nil {
t.Error("expected an error due to invalid primary key getter")
}
Expand All @@ -116,7 +116,7 @@ func TestRegisterModel(t *testing.T) {
t.Run("FieldConfiguration", func(t *testing.T) {
testApp := createTestApp()
testModel := &TestModel1{}
model, err := testApp.RegisterModel(testModel)
model, err := testApp.RegisterModel(testModel, nil)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
Expand All @@ -133,7 +133,7 @@ func TestRegisterModel(t *testing.T) {

t.Run("UnknownTagKey", func(t *testing.T) {
testApp := createTestApp()
_, err := testApp.RegisterModel(&TestModel2{})
_, err := testApp.RegisterModel(&TestModel2{}, nil)
if err != nil {
t.Error("expected no error due to unknown tag key")
}
Expand All @@ -146,7 +146,7 @@ func TestRegisterModel(t *testing.T) {
Name string `admin:"listDisplay:invalid"`
}

_, err := testApp.RegisterModel(&InvalidTagValueModel{})
_, err := testApp.RegisterModel(&InvalidTagValueModel{}, nil)
if err == nil {
t.Error("expected an error due to invalid listDisplay tag value")
}
Expand All @@ -159,7 +159,7 @@ func TestRegisterModel(t *testing.T) {
Name string `admin:"listFetch:invalid"`
}

_, err := testApp.RegisterModel(&InvalidTagFetchModel{})
_, err := testApp.RegisterModel(&InvalidTagFetchModel{}, nil)
if err == nil {
t.Error("expected an error due to invalid listFetch tag value")
}
Expand All @@ -172,7 +172,7 @@ func TestRegisterModel(t *testing.T) {
Name string `admin:"listFetch:exclude"`
}

model, err := testApp.RegisterModel(&ModelExcludeFetch{})
model, err := testApp.RegisterModel(&ModelExcludeFetch{}, nil)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
Expand All @@ -192,15 +192,15 @@ func TestRegisterModel(t *testing.T) {
Status string `admin:"listDisplay:exclude"`
}

modelWithID, err := testApp.RegisterModel(&ModelWithID{})
modelWithID, err := testApp.RegisterModel(&ModelWithID{}, nil)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if !modelWithID.Fields[0].IncludeInListFetch {
t.Error("expected ID field to be included in fetch by default")
}

_, err = testApp.RegisterModel(&ModelWithoutID{})
_, err = testApp.RegisterModel(&ModelWithoutID{}, nil)
if err == nil {
t.Fatalf("expected an error but instead got no error")
}
Expand All @@ -209,7 +209,7 @@ func TestRegisterModel(t *testing.T) {
t.Run("URLSafetyCheck", func(t *testing.T) {
testApp := createTestApp()

_, err := testApp.RegisterModel(&unsafeModelName{})
_, err := testApp.RegisterModel(&unsafeModelName{}, nil)
if err == nil {
t.Error("expected an error due to invalid URL safety of the model's name")
}
Expand All @@ -222,7 +222,7 @@ func TestRegisterModel(t *testing.T) {
ID uint
}

model, err := testApp.RegisterModel(&safeNameModel{})
model, err := testApp.RegisterModel(&safeNameModel{}, nil)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
Expand All @@ -234,7 +234,7 @@ func TestRegisterModel(t *testing.T) {

t.Run("CustomNameAndDisplayName", func(t *testing.T) {
testApp := createTestApp()
model, err := testApp.RegisterModel(&CustomNameModel{})
model, err := testApp.RegisterModel(&CustomNameModel{}, nil)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
Expand All @@ -251,7 +251,7 @@ func TestRegisterModel(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
testApp, err := panel.RegisterApp("TestApp", "Test App")
testApp, err := panel.RegisterApp("TestApp", "Test App", nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
Expand All @@ -270,7 +270,7 @@ func TestRegisterModel(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
testApp, err := panel.RegisterApp("TestApp", "Test App")
testApp, err := panel.RegisterApp("TestApp", "Test App", nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
Expand All @@ -289,7 +289,7 @@ func TestRegisterModel(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
testApp, err := panel.RegisterApp("TestApp", "Test App")
testApp, err := panel.RegisterApp("TestApp", "Test App", nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
Expand Down
19 changes: 10 additions & 9 deletions internal/adminpanel/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (m *Model) GetInstanceDeleteHandler() HandlerFunc {
return GetErrorHTML(http.StatusForbidden, fmt.Errorf("you are not allowed to delete this instance"))
}

err = m.App.Panel.ORM.DeleteInstance(m.PTR, instanceIDInterface)
err = m.GetORM().DeleteInstance(m.PTR, instanceIDInterface)
if err != nil {
return GetErrorHTML(http.StatusInternalServerError, err)
}
Expand Down Expand Up @@ -97,7 +97,7 @@ func (m *Model) GetInstanceViewHandler() HandlerFunc {
}
}

instanceData, err := m.App.Panel.ORM.FetchInstanceOnlyFields(m.PTR, instanceIDInterface, fieldsToFetch)
instanceData, err := m.GetORM().FetchInstanceOnlyFields(m.PTR, instanceIDInterface, fieldsToFetch)
if err != nil {
return GetErrorHTML(http.StatusInternalServerError, err)
}
Expand Down Expand Up @@ -154,7 +154,7 @@ func (f *ModelAddForm) Save(values map[string]form.HTMLType) (interface{}, error
}
}

err = f.Model.App.Panel.ORM.CreateInstanceOnlyFields(instancePtr.Interface(), fieldsToInclude)
err = f.Model.GetORM().CreateInstanceOnlyFields(instancePtr.Interface(), fieldsToInclude)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -203,7 +203,7 @@ func (f *ModelEditForm) Save(values map[string]form.HTMLType) (interface{}, erro
}
}

err = f.Model.App.Panel.ORM.UpdateInstanceOnlyFields(instancePtr.Interface(), fieldsToInclude, f.InstanceID)
err = f.Model.GetORM().UpdateInstanceOnlyFields(instancePtr.Interface(), fieldsToInclude, f.InstanceID)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -318,11 +318,12 @@ func (m *Model) GetAddHandler() HandlerFunc {
}

html, err := m.App.Panel.Config.Renderer.RenderTemplate("new_instance", map[string]interface{}{
"apps": apps, "navBarItems": m.App.Panel.Config.GetNavBarItems(data),
"form": formInstance,
"model": m,
"formErrs": formErrs,
"fieldErrs": fieldErrs,
"apps": apps,
"navBarItems": m.App.Panel.Config.GetNavBarItems(data),
"form": formInstance,
"model": m,
"formErrs": formErrs,
"fieldErrs": fieldErrs,
})
if err != nil {
return GetErrorHTML(http.StatusInternalServerError, err)
Expand Down
12 changes: 10 additions & 2 deletions internal/adminpanel/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ type Model struct {
Fields []FieldConfig
PrimaryKeyGetter func(interface{}) interface{}
PrimaryKeyType reflect.Type
ORM ORMIntegrator
}

func (m *Model) GetORM() ORMIntegrator {
if m.ORM != nil {
return m.ORM
}
return m.App.GetORM()
}

type AdminModelNameInterface interface {
Expand Down Expand Up @@ -90,15 +98,15 @@ func (m *Model) GetViewHandler() HandlerFunc {
searchQuery := m.App.Panel.Web.GetQueryParam(data, "search")
var instances interface{}
if searchQuery == "" {
instances, err = m.App.Panel.ORM.FetchInstancesOnlyFields(m.PTR, fieldsToFetch)
instances, err = m.GetORM().FetchInstancesOnlyFields(m.PTR, fieldsToFetch)
} else {
var fieldsToSearch []string
for _, fieldConfig := range m.Fields {
if fieldConfig.IncludeInSearch {
fieldsToSearch = append(fieldsToSearch, fieldConfig.Name)
}
}
instances, err = m.App.Panel.ORM.FetchInstancesOnlyFieldWithSearch(m.PTR, fieldsToFetch, searchQuery, fieldsToSearch)
instances, err = m.GetORM().FetchInstancesOnlyFieldWithSearch(m.PTR, fieldsToFetch, searchQuery, fieldsToSearch)
}
if err != nil {
return GetErrorHTML(http.StatusInternalServerError, err)
Expand Down
4 changes: 2 additions & 2 deletions internal/adminpanel/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ func TestModel_GetViewHandler(t *testing.T) {
t.Fatalf("expected no error, got %v", err)
}

testApp, err := panel.RegisterApp("TestApp", "Test App")
testApp, err := panel.RegisterApp("TestApp", "Test App", nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

model, err := testApp.RegisterModel(&TestModel{})
model, err := testApp.RegisterModel(&TestModel{}, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
Expand Down
8 changes: 6 additions & 2 deletions internal/adminpanel/panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ type AdminPanel struct {
Config AdminConfig
}

func (ap *AdminPanel) GetORM() ORMIntegrator {
return ap.ORM
}

func NewAdminPanel(orm ORMIntegrator, web WebIntegrator, permissionsCheck PermissionFunc, config *AdminConfig) (*AdminPanel, error) {
if orm == nil {
return nil, fmt.Errorf("orm integrator cannot be nil")
Expand Down Expand Up @@ -82,7 +86,7 @@ func (ap *AdminPanel) GetHandler() HandlerFunc {
}
}

func (ap *AdminPanel) RegisterApp(name, displayName string) (*App, error) {
func (ap *AdminPanel) RegisterApp(name, displayName string, orm ORMIntegrator) (*App, error) {
if _, exists := ap.Apps[name]; exists {
return nil, fmt.Errorf("admin app '%s' already exists. Apps cannot be registered more than once", name)
}
Expand All @@ -91,7 +95,7 @@ func (ap *AdminPanel) RegisterApp(name, displayName string) (*App, error) {
return nil, fmt.Errorf("admin app name '%s' is not URL safe", name)
}

app := &App{Name: name, DisplayName: displayName, Models: make(map[string]*Model), ModelsSlice: make([]*Model, 0), Panel: ap}
app := &App{Name: name, DisplayName: displayName, Models: make(map[string]*Model), ModelsSlice: make([]*Model, 0), Panel: ap, ORM: orm}
ap.Apps[name] = app
ap.AppsSlice = append(ap.AppsSlice, app)
ap.Web.HandleRoute("GET", ap.Config.GetPrefix()+app.GetLink(), app.GetHandler())
Expand Down
6 changes: 3 additions & 3 deletions internal/adminpanel/panel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func TestAdminPanel_RegisterApp(t *testing.T) {
t.Fatalf("expected no error, got %v", err)
}

app, err := panel.RegisterApp("TestApp", "Test App")
app, err := panel.RegisterApp("TestApp", "Test App", nil)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
Expand All @@ -85,12 +85,12 @@ func TestAdminPanel_RegisterApp(t *testing.T) {
t.Fatalf("expected app to be registered, got nil")
}

_, err = panel.RegisterApp("TestApp", "Test App Duplicate")
_, err = panel.RegisterApp("TestApp", "Test App Duplicate", nil)
if err == nil || err.Error() != "admin app 'TestApp' already exists. Apps cannot be registered more than once" {
t.Fatalf("expected error when registering the same app twice, got %v", err)
}

_, err = panel.RegisterApp("Unsafe App!", "Unsafe App")
_, err = panel.RegisterApp("Unsafe App!", "Unsafe App", nil)
if err == nil || err.Error() != "admin app name 'Unsafe App!' is not URL safe" {
t.Fatalf("expected error for unsafe app name, got %v", err)
}
Expand Down
Loading

0 comments on commit d6820f2

Please sign in to comment.