Files
relspecgo/pkg/writers/graphql/relationships.go
2025-12-28 11:41:55 +02:00

179 lines
4.4 KiB
Go

package graphql
import (
"fmt"
"strings"
"git.warky.dev/wdevs/relspecgo/pkg/models"
)
func (w *Writer) generateRelationFields(table *models.Table, db *models.Database, schema *models.Schema) []string {
var fields []string
// 1. Forward relationships (this table has FK)
for _, constraint := range table.Constraints {
if constraint.Type != models.ForeignKeyConstraint {
continue
}
// Find the related table
relatedTable := w.findTable(db, constraint.ReferencedSchema, constraint.ReferencedTable)
if relatedTable == nil {
continue
}
// Generate field name (remove "Id" suffix from FK column if present)
fieldName := w.relationFieldName(constraint.Columns[0])
// Determine nullability from FK column
nullable := true
for _, colName := range constraint.Columns {
if col, exists := table.Columns[colName]; exists {
if col.NotNull {
nullable = false
break
}
}
}
// Format: fieldName: RelatedType! or fieldName: RelatedType
gqlType := relatedTable.Name
if !nullable {
gqlType += "!"
}
fields = append(fields, fmt.Sprintf(" %s: %s", fieldName, gqlType))
}
// 2. Reverse relationships (other tables reference this table)
for _, otherSchema := range db.Schemas {
for _, otherTable := range otherSchema.Tables {
if otherTable.Name == table.Name && otherSchema.Name == schema.Name {
continue
}
// Skip join tables for many-to-many
if w.isJoinTable(otherTable) {
// Check if this is a many-to-many through this join table
if m2mField := w.getManyToManyField(table, otherTable, db); m2mField != "" {
fields = append(fields, m2mField)
}
continue
}
for _, constraint := range otherTable.Constraints {
if constraint.Type == models.ForeignKeyConstraint &&
constraint.ReferencedTable == table.Name &&
constraint.ReferencedSchema == schema.Name {
// Add reverse relationship field (array)
fieldName := w.pluralize(w.camelCase(otherTable.Name))
fields = append(fields, fmt.Sprintf(" %s: [%s!]!", fieldName, otherTable.Name))
}
}
}
}
return fields
}
func (w *Writer) getManyToManyField(table *models.Table, joinTable *models.Table, db *models.Database) string {
// Find the two FK constraints in the join table
var fk1, fk2 *models.Constraint
for _, constraint := range joinTable.Constraints {
if constraint.Type == models.ForeignKeyConstraint {
if fk1 == nil {
fk1 = constraint
} else {
fk2 = constraint
}
}
}
if fk1 == nil || fk2 == nil {
return ""
}
// Determine which FK points to our table and which to the other table
var targetConstraint *models.Constraint
if fk1.ReferencedTable == table.Name {
targetConstraint = fk2
} else if fk2.ReferencedTable == table.Name {
targetConstraint = fk1
} else {
return "" // This join table doesn't involve our table
}
// Find the target table
targetTable := w.findTable(db, targetConstraint.ReferencedSchema, targetConstraint.ReferencedTable)
if targetTable == nil {
return ""
}
// Generate many-to-many field
fieldName := w.pluralize(w.camelCase(targetTable.Name))
return fmt.Sprintf(" %s: [%s!]!", fieldName, targetTable.Name)
}
func (w *Writer) findTable(db *models.Database, schemaName, tableName string) *models.Table {
for _, schema := range db.Schemas {
if schema.Name != schemaName {
continue
}
for _, table := range schema.Tables {
if table.Name == tableName {
return table
}
}
}
return nil
}
func (w *Writer) relationFieldName(fkColumnName string) string {
// Remove "Id" or "_id" suffix
name := fkColumnName
if strings.HasSuffix(name, "Id") {
name = name[:len(name)-2]
} else if strings.HasSuffix(name, "_id") {
name = name[:len(name)-3]
}
return w.camelCase(name)
}
func (w *Writer) camelCase(s string) string {
// If already camelCase or PascalCase, convert to camelCase
if s == "" {
return s
}
// Convert first character to lowercase
return strings.ToLower(string(s[0])) + s[1:]
}
func (w *Writer) pluralize(s string) string {
// Simple pluralization rules
if s == "" {
return s
}
// Already plural
if strings.HasSuffix(s, "s") {
return s
}
// Words ending in 'y' → 'ies'
if strings.HasSuffix(s, "y") {
return s[:len(s)-1] + "ies"
}
// Words ending in 's', 'x', 'z', 'ch', 'sh' → add 'es'
if strings.HasSuffix(s, "s") || strings.HasSuffix(s, "x") ||
strings.HasSuffix(s, "z") || strings.HasSuffix(s, "ch") ||
strings.HasSuffix(s, "sh") {
return s + "es"
}
// Default: add 's'
return s + "s"
}