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" }