feat(09-10): add typed relation manager

This commit is contained in:
Jakub Zych
2026-09-26 21:33:43 +02:00
parent d7d48e2fbe
commit 12081c18d1
7 changed files with 1176 additions and 12 deletions

View File

@@ -111,7 +111,7 @@ func ExecuteList(ctx context.Context, db *gorm.DB, cc *CompiledController, in Li
q = next
}
}
q, err = applyListSearch(q, cc.List, model, in.Search)
q, err = applyListSearch(q, cc.Controller, cc.List, model, in.Search)
if err != nil {
return nil, err
}
@@ -123,7 +123,7 @@ func ExecuteList(ctx context.Context, db *gorm.DB, cc *CompiledController, in Li
if err := q.Session(&gorm.Session{}).Count(&total).Error; err != nil {
return nil, err
}
q = applyListOrder(q, cc.List, model, sortCol, sortDesc)
q = applyListOrder(q, cc.Controller, cc.List, model, sortCol, sortDesc)
for _, col := range cc.List.Columns {
if col.Relation == "" {
continue
@@ -140,7 +140,7 @@ func ExecuteList(ctx context.Context, db *gorm.DB, cc *CompiledController, in Li
values := slice.Elem()
data := make([]map[string]any, 0, values.Len())
for i := 0; i < values.Len(); i++ {
data = append(data, projectRow(values.Index(i).Addr().Interface(), cc.List.Columns))
data = append(data, projectRow(values.Index(i).Addr().Interface(), cc.Controller, cc.List.Columns))
}
paged := lagoon.Paginate(data, page, per, total)
return &ListResult{
@@ -223,7 +223,7 @@ func normalizeSort(schema *ListSchema, in ListInput) (string, bool, error) {
return match.Key, dir == "desc", nil
}
func applyListOrder(db *gorm.DB, schema *ListSchema, model any, column string, desc bool) *gorm.DB {
func applyListOrder(db *gorm.DB, controller pact.AdminController, schema *ListSchema, model any, column string, desc bool) *gorm.DB {
table := tableName(model)
pk := primaryColumn(model)
if column != "" {
@@ -238,7 +238,7 @@ func applyListOrder(db *gorm.DB, schema *ListSchema, model any, column string, d
continue
}
orderTable = field
orderColumn = col.Select
orderColumn = listRelationColumn(controller, col.Relation, col.Select)
db = db.Joins(field)
break
}
@@ -253,7 +253,7 @@ func applyListOrder(db *gorm.DB, schema *ListSchema, model any, column string, d
return db
}
func applyListSearch(db *gorm.DB, schema *ListSchema, model any, term string) (*gorm.DB, error) {
func applyListSearch(db *gorm.DB, controller pact.AdminController, schema *ListSchema, model any, term string) (*gorm.DB, error) {
term = strings.TrimSpace(term)
if term == "" {
return db, nil
@@ -277,12 +277,13 @@ func applyListSearch(db *gorm.DB, schema *ListSchema, model any, term string) (*
column := col.Key
if col.Relation != "" {
field, ok := relationFieldName(model, col.Relation)
if !ok || !identifier(col.Select) {
physical := listRelationColumn(controller, col.Relation, col.Select)
if !ok || !identifier(physical) {
return nil, listInvalid("search", "is not searchable")
}
// GORM aliases the joined association with the Go field name.
table = field
column = col.Select
column = physical
if _, done := joined[field]; !done {
db = db.Joins(field)
joined[field] = struct{}{}
@@ -496,7 +497,7 @@ func primaryColumn(model any) string {
return "id"
}
func relatedSelect(row reflect.Value, col ListColumn) (any, bool) {
func relatedSelect(row reflect.Value, controller pact.AdminController, col ListColumn) (any, bool) {
for row.Kind() == reflect.Pointer {
if row.IsNil() {
return nil, false
@@ -523,9 +524,21 @@ func relatedSelect(row reflect.Value, col ListColumn) (any, bool) {
if field.Kind() != reflect.Struct {
return nil, false
}
selected := fieldByColumn(field, col.Select)
selected := fieldByColumn(field, listRelationColumn(controller, col.Relation, col.Select))
if !selected.IsValid() || !selected.CanInterface() {
return nil, false
}
return selected.Interface(), true
}
func listRelationColumn(controller pact.AdminController, relation, column string) string {
mapper, ok := controller.(pact.ListRelationColumnMapper)
if !ok || mapper == nil {
return column
}
mapped, ok := mapper.ListRelationColumn(relation, column)
if !ok || mapped == "" {
return column
}
return mapped
}