Skip to content
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
d33c370
Update qor5/x/v3 and improve ServeHTTP error handling
molon Sep 8, 2025
82205c9
Add sync.Once to Builder ServeHTTP initialization
molon Sep 8, 2025
fc0ec93
Handle nil handler in Builder.ServeHTTP
molon Sep 8, 2025
8fa961e
Add starter package and update dependencies
molon Sep 22, 2025
3fc0042
Update github.com/qor5/x/v3
molon Sep 23, 2025
3419d9b
Refactor S3 storage setup to use s3x package
molon Sep 23, 2025
d6f331f
Use PascalCase for field names in validation errors
molon Sep 23, 2025
f95429e
Update github.com/qor5/x/v3 to latest version
molon Sep 26, 2025
2812287
Merge branch 'fix-preset-build' into field-err
molon Sep 26, 2025
eee1e40
Refactor handler hooks and improve user role saving
molon Sep 26, 2025
4443a5a
Fix error handling in grpcWrapper.convert method to ensure proper val…
molon Sep 29, 2025
67eb860
add filterNotification
buggoing Oct 11, 2025
7c63157
Merge branch 'main' into field-err
zhangshanwen Oct 16, 2025
2459c7f
Update dependencies in go.mod and go.sum
zhangshanwen Oct 16, 2025
e75aecc
Update dependency versions in go.sum
zhangshanwen Oct 16, 2025
2b0a7dd
Update S3 client setup to include nil parameter
zhangshanwen Oct 16, 2025
ce99c3c
Add validation and filter notification examples
zhangshanwen Oct 16, 2025
ced4fc3
style: format code with Gofumpt
deepsource-autofix[bot] Oct 16, 2025
e425e0b
Change validation error from field to global error
zhangshanwen Oct 16, 2025
cdfab8b
Merge branch 'field-err' of https://github.com/qor5/admin into field-err
zhangshanwen Oct 16, 2025
6ebeb52
Add test for ServeHTTP nil handler error handling
zhangshanwen Oct 16, 2025
a2fbc61
style: format code with Gofumpt
deepsource-autofix[bot] Oct 16, 2025
7a58501
Add embedded default config and config loader
zhangshanwen Oct 17, 2025
5287b1f
Rename product_test.go to pagebuilder_test.go
zhangshanwen Oct 17, 2025
4a76d33
Add tests for auth and user listing features
zhangshanwen Oct 17, 2025
7fc5981
style: format code with Gofumpt
deepsource-autofix[bot] Oct 17, 2025
8ea78bd
Fix TOTP revocation and expand user tests
zhangshanwen Oct 17, 2025
d902341
Add gRPC DataOperator example and expand user tests
zhangshanwen Oct 17, 2025
159eecd
style: format code with Gofumpt
deepsource-autofix[bot] Oct 17, 2025
39f320b
Remove unused PresetsDataOperatorWithGRPC example
zhangshanwen Oct 17, 2025
58850c0
Merge branch 'field-err' of https://github.com/qor5/admin into field-err
zhangshanwen Oct 17, 2025
0995d11
Add mock GRPC DataOperator example and tests
zhangshanwen Oct 17, 2025
c7fe83d
Add login page test and update test config for auth
zhangshanwen Oct 17, 2025
bd980c8
style: format code with Gofumpt
deepsource-autofix[bot] Oct 17, 2025
ed4a5e1
Enable debug in tests and add auth reset tests
zhangshanwen Oct 20, 2025
99c4942
Add tests for password reset edge cases
zhangshanwen Oct 20, 2025
e621abc
Remove redundant comments in auth_test.go
zhangshanwen Oct 20, 2025
9984f09
Refactor reset password test for short password failure
zhangshanwen Oct 20, 2025
0a6c47a
style: format code with Gofumpt
deepsource-autofix[bot] Oct 20, 2025
ed54f5b
Merge branch 'main' into field-err
zhangshanwen Oct 20, 2025
5f0a6ee
Update dependencies in go.mod and go.sum
zhangshanwen Oct 20, 2025
28c3754
Merge branch 'main' into field-err
zhangshanwen Oct 20, 2025
4ebde56
Update qor5/x dependency and improve reset password tests
zhangshanwen Oct 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/docsrc/examples/examples_presets/detailing.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,3 +436,28 @@ func PresetsDetailDisableSave(b *presets.Builder, db *gorm.DB) (
dpc.Section(sec)
return
}

func PresetsDetailSaverValidation(b *presets.Builder, db *gorm.DB) (
cust *presets.ModelBuilder,
cl *presets.ListingBuilder,
ce *presets.EditingBuilder,
dp *presets.DetailingBuilder,
) {
cust, cl, ce, dp = PresetsHelloWorld(b, db)
dp = cust.Detailing("Customer")
section := presets.NewSectionBuilder(cust, "Customer").Editing("Name", "Email")
section.WrapSaveFunc(func(in presets.SaveFunc) presets.SaveFunc {
return func(obj interface{}, id string, ctx *web.EventContext) (err error) {
ve := web.ValidationErrors{}
if obj.(*Customer).Name == "system" {
ve.GlobalError("You can not use system as name")
}
if ve.HaveErrors() {
return &ve
}
return in(obj, id, ctx)
}
})
dp.Section(section)
return
}
25 changes: 25 additions & 0 deletions docs/docsrc/examples/examples_presets/detailing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -908,3 +908,28 @@ func TestPresetsDetailDisableSave(t *testing.T) {
})
}
}

func TestPresetsDetailSaverValidation(t *testing.T) {
pb := presets.New().DataOperator(gorm2op.DataOperator(TestDB))
PresetsDetailSaverValidation(pb, TestDB)

cases := []multipartestutils.TestCase{
{
Name: "detail saver validation",
ReqFunc: func() *http.Request {
customPageData.TruncatePut(SqlDB)
return multipartestutils.NewMultipartBuilder().
PageURL("/customers").
EventFunc("section_save_Customer").
AddField("Name", "system").
BuildEventFuncRequest()
},
ExpectRunScriptContainsInOrder: []string{"You can not use system as name"},
},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
multipartestutils.RunCase(t, c, pb)
})
}
}
21 changes: 21 additions & 0 deletions docs/docsrc/examples/examples_presets/editing.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,24 @@ func PresetsEditingSection(b *presets.Builder, db *gorm.DB) (
edit.Section(section.Clone())
return
}

func PresetsEditingSaverValidation(b *presets.Builder, db *gorm.DB) (mb *presets.ModelBuilder,
cl *presets.ListingBuilder,
ce *presets.EditingBuilder,
dp *presets.DetailingBuilder,
) {
b.DataOperator(gorm2op.DataOperator(db))
db.AutoMigrate(&Company{})
mb = b.Model(&Company{})
mb.Editing().SaveFunc(func(obj interface{}, id string, ctx *web.EventContext) (err error) {
ve := web.ValidationErrors{}
if obj.(*Company).Name == "system" {
ve.FieldError("Name", "You can not use system as name")
}
if ve.HaveErrors() {
return &ve
}
return nil
})
return
}
24 changes: 24 additions & 0 deletions docs/docsrc/examples/examples_presets/editing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -522,3 +522,27 @@ func TestCreateHTMLSanitizerSetterFunc(t *testing.T) {
t.Errorf("Expected %q, got %q", expected, customer.HTMLSanitizerPolicyTiptapInput)
}
}

func TestPresetsEditingSaver(t *testing.T) {
pb := presets.New().DataOperator(gorm2op.DataOperator(TestDB))
PresetsEditingSaverValidation(pb, TestDB)

cases := []multipartestutils.TestCase{
{
Name: "saver return error",
ReqFunc: func() *http.Request {
return multipartestutils.NewMultipartBuilder().
PageURL("/companies?__execute_event__=presets_Update").
AddField("Name", "system").
BuildEventFuncRequest()
},
ExpectPortalUpdate0ContainsInOrder: []string{`You can not use system as name`},
},
}

for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
multipartestutils.RunCase(t, c, pb)
})
}
}
69 changes: 69 additions & 0 deletions docs/docsrc/examples/examples_presets/listing.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"gorm.io/gorm"

"github.com/qor5/x/v3/i18n"
"github.com/qor5/x/v3/statusx"
v "github.com/qor5/x/v3/ui/vuetify"
"github.com/qor5/x/v3/ui/vuetifyx"

Expand Down Expand Up @@ -480,3 +481,71 @@ func PresetsListingDatatableFunc(b *presets.Builder, db *gorm.DB) (
}

// @snippet_end

// @snippet_begin(PresetsListingFilterNotificationFuncSample)

func PresetsListingFilterNotificationFunc(b *presets.Builder, db *gorm.DB) (
mb *presets.ModelBuilder,
cl *presets.ListingBuilder,
ce *presets.EditingBuilder,
dp *presets.DetailingBuilder,
) {
b.DataOperator(gorm2op.DataOperator(db))
err := db.AutoMigrate(&Customer{})
if err != nil {
panic(err)
}
mb = b.Model(&Customer{})
mb.Listing().FilterNotificationFunc(func(_ *web.EventContext) h.HTMLComponent {
return h.Div().Text("Filter Notification").Class(fmt.Sprintf("text-%s", v.ColorWarning))
})
return
}

// @snippet_end
type mockGRPCDataOperatorWrapper struct {
next presets.DataOperator
}

func mockGRPCDataOperator(next presets.DataOperator) presets.DataOperator {
return &mockGRPCDataOperatorWrapper{next: next}
}

func (w *mockGRPCDataOperatorWrapper) Search(eventCtx *web.EventContext, params *presets.SearchParams) (*presets.SearchResult, error) {
return w.next.Search(eventCtx, params)
}

func (w *mockGRPCDataOperatorWrapper) Fetch(obj any, id string, eventCtx *web.EventContext) (any, error) {
return w.next.Fetch(obj, id, eventCtx)
}

func (w *mockGRPCDataOperatorWrapper) Save(obj any, id string, eventCtx *web.EventContext) error {
var fvs statusx.FieldViolations
p := obj.(*Customer)
if p.Name == "system" {
fvs = append(fvs, statusx.NewFieldViolation("Name", "name can`t set system", "name can`t set system"))
return statusx.BadRequest(fvs).Err()
}
return nil
}

func (w *mockGRPCDataOperatorWrapper) Delete(obj any, id string, eventCtx *web.EventContext) error {
return w.next.Delete(obj, id, eventCtx)
}

func PresetsDataOperatorWithGRPC(b *presets.Builder, db *gorm.DB) (
mb *presets.ModelBuilder,
cl *presets.ListingBuilder,
ce *presets.EditingBuilder,
dp *presets.DetailingBuilder,
) {
b.DataOperator(presets.DataOperatorWithGRPC(mockGRPCDataOperator(gorm2op.DataOperator(db))))
err := db.AutoMigrate(&Customer{})
if err != nil {
panic(err)
}
mb = b.Model(&Customer{})
cl = mb.Listing()
ce = mb.Editing()
return
}
76 changes: 76 additions & 0 deletions docs/docsrc/examples/examples_presets/listing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/theplant/gofixtures"

"github.com/qor5/admin/v3/presets"
"github.com/qor5/admin/v3/presets/actions"
"github.com/qor5/admin/v3/presets/gorm2op"
)

Expand Down Expand Up @@ -171,3 +172,78 @@ func TestPresetsListingDatatableFunc(t *testing.T) {
})
}
}

func TestPresetsListingFilterNotificationFunc(t *testing.T) {
pb := presets.New().DataOperator(gorm2op.DataOperator(TestDB))
PresetsListingFilterNotificationFunc(pb, TestDB)
cases := []multipartestutils.TestCase{
{
Name: "Filter Notification",
Debug: true,
ReqFunc: func() *http.Request {
listingDatatableData.TruncatePut(SqlDB)
return multipartestutils.NewMultipartBuilder().
PageURL("/customers").
BuildEventFuncRequest()
},
ExpectPageBodyContainsInOrder: []string{`Filter Notification`},
},
}

for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
multipartestutils.RunCase(t, c, pb)
})
}
}

func TestPresetsDataOperatorWithGRPC(t *testing.T) {
pb := presets.New().DataOperator(gorm2op.DataOperator(TestDB))
PresetsDataOperatorWithGRPC(pb, TestDB)
cases := []multipartestutils.TestCase{
{
Name: "Index Customer",
Debug: true,
ReqFunc: func() *http.Request {
listingDatatableData.TruncatePut(SqlDB)
return multipartestutils.NewMultipartBuilder().
PageURL("/customers").
BuildEventFuncRequest()
},
ExpectPageBodyContainsInOrder: []string{`v-card`, `Felix 1`, `[email protected]`},
},
{
Name: "Update Customer",
Debug: true,
ReqFunc: func() *http.Request {
listingDatatableData.TruncatePut(SqlDB)
return multipartestutils.NewMultipartBuilder().
PageURL("/customers").
Query(presets.ParamID, "12").
EventFunc(actions.Update).
AddField("Name", "system").
BuildEventFuncRequest()
},
ExpectPortalUpdate0ContainsInOrder: []string{"invalid argument"},
},
{
Name: "Delete Customer",
Debug: true,
ReqFunc: func() *http.Request {
listingDatatableData.TruncatePut(SqlDB)
return multipartestutils.NewMultipartBuilder().
PageURL("/customers").
Query(presets.ParamID, "12").
EventFunc(actions.DoDelete).
BuildEventFuncRequest()
},
ExpectRunScriptContainsInOrder: []string{"PresetsNotifModelsDeletedexamplesPresetsCustomer"},
},
}

for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
multipartestutils.RunCase(t, c, pb)
})
}
}
2 changes: 1 addition & 1 deletion docs/docsrc/examples/examples_presets/menu_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func TestPresetsPresetsMenuComponent(t *testing.T) {
cases := []multipartestutils.TestCase{
{
Name: "multiple openStrategy",
Debug: false,
Debug: true,
ReqFunc: func() *http.Request {
return multipartestutils.NewMultipartBuilder().
PageURL("/presets-menu-component/books").
Expand Down
4 changes: 4 additions & 0 deletions docs/docsrc/examples/examples_presets/mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ func SamplesHandler(mux examples.Muxer) {
addExample(mux, db, PresetsEditingValidate)
addExample(mux, db, PresetsEditingSetter)
addExample(mux, db, PresetsEditingSection)
addExample(mux, db, PresetsEditingSaverValidation)
addExample(mux, db, PresetsListingCustomizationSearcher)
addExample(mux, db, PresetsListingDatatableFunc)
addExample(mux, db, PresetsListingFilterNotificationFunc)
addExample(mux, db, PresetsDetailInlineEditDetails)
addExample(mux, db, PresetsDetailSectionView)
addExample(mux, db, PresetsDetailTabsSection)
Expand All @@ -65,6 +67,8 @@ func SamplesHandler(mux examples.Muxer) {
addExample(mux, db, PresetsCustomPage)
addExample(mux, db, PresetsPlainNestedField)
addExample(mux, db, PresetsDetailDisableSave)
addExample(mux, db, PresetsDetailSaverValidation)
addExample(mux, db, PresetsDataOperatorWithGRPC)
return
}

Expand Down
8 changes: 4 additions & 4 deletions example/integration/login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestLogin(t *testing.T) {
cases := []multipartestutils.TestCase{
{
Name: "view by en",
Debug: false,
Debug: true,
ReqFunc: func() *http.Request {
req := multipartestutils.NewMultipartBuilder().
PageURL("/auth/login").
Expand All @@ -36,7 +36,7 @@ func TestLogin(t *testing.T) {
},
{
Name: "view by zh",
Debug: false,
Debug: true,
ReqFunc: func() *http.Request {
req := multipartestutils.NewMultipartBuilder().
PageURL("/auth/login").
Expand All @@ -48,7 +48,7 @@ func TestLogin(t *testing.T) {
},
{
Name: "view by ja",
Debug: false,
Debug: true,
ReqFunc: func() *http.Request {
req := multipartestutils.NewMultipartBuilder().
PageURL("/auth/login").
Expand All @@ -60,7 +60,7 @@ func TestLogin(t *testing.T) {
},
{
Name: "view by en (customized)",
Debug: false,
Debug: true,
HandlerMaker: func() http.Handler {
mux := http.NewServeMux()
c := admin.NewConfig(TestDB, false)
Expand Down
Loading
Loading