diff --git a/internal/action/action_test.go b/internal/action/action_test.go index f285acca5..b1806b130 100644 --- a/internal/action/action_test.go +++ b/internal/action/action_test.go @@ -579,6 +579,129 @@ func TestOverriddenRequiredActionField(t *testing.T) { ) } +func TestPrivateFieldExposedToActions(t *testing.T) { + actionInfo := testhelper.ParseActionInfoForTest( + t, + map[string]string{ + "user.ts": testhelper.GetCodeWithSchema( + `import {Schema, FieldMap, StringType, Action, ActionOperation, BaseEntSchema, requiredField} from "{schema}"; + + export default class User extends BaseEntSchema { + fields: FieldMap = { + FirstName: StringType(), + LastName: StringType(), + EmailAddress: StringType(), + Password: StringType({ + private: { + exposeToActions: true, + }, + }), + }; + + actions: Action[] = [ + { + operation: ActionOperation.Create, + }, + ]; + } + `, + ), + }, + base.TypeScript, + "UserConfig", + ) + + verifyExpectedActions( + t, + actionInfo, + []expectedAction{ + { + name: "CreateUserAction", + fields: []expectedField{ + { + name: "FirstName", + tsType: "string", + gqlType: "String!", + }, + { + name: "LastName", + tsType: "string", + gqlType: "String!", + }, + { + name: "EmailAddress", + tsType: "string", + gqlType: "String!", + }, + { + name: "Password", + tsType: "string", + gqlType: "String!", + }, + }, + }, + }, + ) +} + +func TestPrivateField(t *testing.T) { + actionInfo := testhelper.ParseActionInfoForTest( + t, + map[string]string{ + "user.ts": testhelper.GetCodeWithSchema( + `import {Schema, FieldMap, StringType, Action, ActionOperation, BaseEntSchema, requiredField} from "{schema}"; + + export default class User extends BaseEntSchema { + fields: FieldMap = { + FirstName: StringType(), + LastName: StringType(), + EmailAddress: StringType(), + Password: StringType({ + private: true, + }), + }; + + actions: Action[] = [ + { + operation: ActionOperation.Create, + }, + ]; + } + `, + ), + }, + base.TypeScript, + "UserConfig", + ) + + verifyExpectedActions( + t, + actionInfo, + []expectedAction{ + { + name: "CreateUserAction", + fields: []expectedField{ + { + name: "FirstName", + tsType: "string", + gqlType: "String!", + }, + { + name: "LastName", + tsType: "string", + gqlType: "String!", + }, + { + name: "EmailAddress", + tsType: "string", + gqlType: "String!", + }, + }, + }, + }, + ) +} + func TestOverriddenOptionalActionField(t *testing.T) { actionInfo := testhelper.ParseActionInfoForTest( t, diff --git a/internal/field/field_type.go b/internal/field/field_type.go index 0db37ee33..43d08df95 100644 --- a/internal/field/field_type.go +++ b/internal/field/field_type.go @@ -100,7 +100,7 @@ func newFieldFromInput(cfg codegenapi.Config, nodeName string, f *input.Field) ( nullable: f.Nullable, dbName: f.StorageKey, hideFromGraphQL: f.HideFromGraphQL, - private: f.Private, + private: f.Private != nil, polymorphic: f.Polymorphic, index: f.Index, graphQLName: f.GraphQLName, @@ -172,7 +172,7 @@ func newFieldFromInput(cfg codegenapi.Config, nodeName string, f *input.Field) ( } if ret.private { - ret.setPrivate() + ret.setPrivate(f.Private) } getSchemaName := func(config string) string { @@ -771,10 +771,10 @@ func (f *Field) setTsFieldType(fieldType enttype.Type) error { return nil } -func (f *Field) setPrivate() { +func (f *Field) setPrivate(p *input.PrivateOptions) { f.private = true f.hideFromGraphQL = true - f.exposeToActionsByDefault = false + f.exposeToActionsByDefault = p.ExposeToActions } func (f *Field) AddInverseEdge(cfg codegenapi.Config, edge *edge.AssociationEdge) error { diff --git a/internal/field/new_field_api.go b/internal/field/new_field_api.go index 10987acc1..6a75ec9b8 100644 --- a/internal/field/new_field_api.go +++ b/internal/field/new_field_api.go @@ -126,7 +126,7 @@ func modifyFieldForDataType( } // if the datatype is specifically private, field makes it private if res.private { - f.Private = true + f.Private = &input.PrivateOptions{} } f.DataTypePkgPath = getImportedPackageThatMatchesIdent(pkg, info.PkgName, info.IdentName).PkgPath diff --git a/internal/schema/input/compare.go b/internal/schema/input/compare.go index 021e9d3eb..4a229a8be 100644 --- a/internal/schema/input/compare.go +++ b/internal/schema/input/compare.go @@ -45,7 +45,7 @@ func fieldEqual(existingField, field *Field) bool { existingField.StorageKey == field.StorageKey && existingField.Unique == field.Unique && existingField.HideFromGraphQL == field.HideFromGraphQL && - existingField.Private == field.Private && + PrivateOptionsEqual(existingField.Private, field.Private) && existingField.GraphQLName == field.GraphQLName && existingField.Index == field.Index && existingField.PrimaryKey == field.PrimaryKey && @@ -132,6 +132,15 @@ func PolymorphicOptionsEqual(existing, p *PolymorphicOptions) bool { existing.DisableBuilderType == p.DisableBuilderType } +func PrivateOptionsEqual(existing, p *PrivateOptions) bool { + ret := change.CompareNilVals(existing == nil, p == nil) + if ret != nil { + return *ret + } + + return existing.ExposeToActions == p.ExposeToActions +} + func assocEdgesEqual(existing, edges []*AssocEdge) bool { if len(existing) != len(edges) { return false diff --git a/internal/schema/input/input.go b/internal/schema/input/input.go index e3941c282..007ee2cbf 100644 --- a/internal/schema/input/input.go +++ b/internal/schema/input/input.go @@ -126,13 +126,13 @@ type Field struct { Nullable bool `json:"nullable,omitempty"` StorageKey string `json:"storageKey,omitempty"` // TODO need a way to indicate unique edge is Required also. this changes type generated in ent and graphql - Unique bool `json:"unique,omitempty"` - HideFromGraphQL bool `json:"hideFromGraphQL,omitempty"` - Private bool `json:"private,omitempty"` - GraphQLName string `json:"graphqlName,omitempty"` - Index bool `json:"index,omitempty"` - PrimaryKey bool `json:"primaryKey,omitempty"` - DefaultToViewerOnCreate bool `json:"defaultToViewerOnCreate,omitempty"` + Unique bool `json:"unique,omitempty"` + HideFromGraphQL bool `json:"hideFromGraphQL,omitempty"` + Private *PrivateOptions `json:"private,omitempty"` + GraphQLName string `json:"graphqlName,omitempty"` + Index bool `json:"index,omitempty"` + PrimaryKey bool `json:"primaryKey,omitempty"` + DefaultToViewerOnCreate bool `json:"defaultToViewerOnCreate,omitempty"` FieldEdge *FieldEdge `json:"fieldEdge,omitempty"` // this only really makes sense on id fields... ForeignKey *ForeignKey `json:"foreignKey,omitempty"` @@ -198,6 +198,10 @@ type PolymorphicOptions struct { DisableBuilderType bool `json:"disableBuilderType,omitempty"` } +type PrivateOptions struct { + ExposeToActions bool `json:"exposeToActions,omitempty"` +} + func getTypeFor(nodeName, fieldName string, typ *FieldType, nullable bool, foreignKey *ForeignKey) (enttype.TSGraphQLType, error) { switch typ.DBType { case UUID: diff --git a/internal/schema/input/parse_ts_field_test.go b/internal/schema/input/parse_ts_field_test.go index e2da95acc..4ee29c7d2 100644 --- a/internal/schema/input/parse_ts_field_test.go +++ b/internal/schema/input/parse_ts_field_test.go @@ -200,7 +200,34 @@ func TestParseFields(t *testing.T) { { name: "password", dbType: input.String, - private: true, + private: &input.PrivateOptions{}, + }, + }, + }, + }, + }, + "private field exposed to actions": { + code: map[string]string{ + "user.ts": getCodeWithSchema(` + import {Schema, FieldMap, StringType} from "{schema}"; + + export default class User implements Schema { + fields: FieldMap = { + password: StringType({private: { + exposeToActions: true, + }}), + }; + }`), + }, + expectedNodes: map[string]node{ + "User": { + fields: []field{ + { + name: "password", + dbType: input.String, + private: &input.PrivateOptions{ + ExposeToActions: true, + }, }, }, }, @@ -331,7 +358,7 @@ func TestParseFields(t *testing.T) { field{ name: "password", dbType: input.String, - private: true, + private: &input.PrivateOptions{}, hideFromGraphQL: true, }, ), diff --git a/internal/schema/input/parse_ts_test.go b/internal/schema/input/parse_ts_test.go index a852023bc..11b76322c 100644 --- a/internal/schema/input/parse_ts_test.go +++ b/internal/schema/input/parse_ts_test.go @@ -40,7 +40,7 @@ type field struct { storageKey string unique bool hideFromGraphQL bool - private bool + private *input.PrivateOptions graphqlName string index bool primaryKey bool @@ -210,7 +210,12 @@ func verifyField(t *testing.T, expField field, field *input.Field) { assert.Equal(t, expField.nullable, field.Nullable) assert.Equal(t, expField.unique, field.Unique) assert.Equal(t, expField.hideFromGraphQL, field.HideFromGraphQL) - assert.Equal(t, expField.private, field.Private) + if expField.private == nil { + require.Nil(t, field.Private) + } else { + require.NotNil(t, field.Private) + assert.Equal(t, expField.private, field.Private) + } assert.Equal(t, expField.graphqlName, field.GraphQLName) assert.Equal(t, expField.index, field.Index) assert.Equal(t, expField.primaryKey, field.PrimaryKey) diff --git a/ts/package.json b/ts/package.json index ea51765ab..5f3aca24d 100644 --- a/ts/package.json +++ b/ts/package.json @@ -1,6 +1,6 @@ { "name": "@snowtop/ent", - "version": "0.1.0-alpha58", + "version": "0.1.0-alpha59", "description": "snowtop ent framework", "main": "index.js", "types": "index.d.ts", diff --git a/ts/src/parse_schema/parse.ts b/ts/src/parse_schema/parse.ts index de96923a2..9e4f7660b 100644 --- a/ts/src/parse_schema/parse.ts +++ b/ts/src/parse_schema/parse.ts @@ -43,6 +43,17 @@ function processFields( } else { delete f.polymorphic; } + if (field.private) { + // convert boolean into object + // we keep boolean as an option to keep API simple + if (typeof field.private === "boolean") { + f.private = {}; + } else { + f.private = field.private; + } + } else { + delete f.private; + } // convert string to object to make API consumed by go simple if (f.fieldEdge && f.fieldEdge.inverseEdge) { if (typeof f.fieldEdge.inverseEdge === "string") { diff --git a/ts/src/schema/schema.ts b/ts/src/schema/schema.ts index fd0b6c0ea..747619dda 100644 --- a/ts/src/schema/schema.ts +++ b/ts/src/schema/schema.ts @@ -367,6 +367,10 @@ export interface FieldEdge { disableBuilderType?: boolean; } +interface PrivateOptions { + exposeToActions?: boolean; +} + // FieldOptions are configurable options for fields. // Can be combined with options for specific field types as neededs export interface FieldOptions { @@ -376,7 +380,10 @@ export interface FieldOptions { serverDefault?: any; unique?: boolean; hideFromGraphQL?: boolean; - private?: boolean; + // private automatically hides from graphql and actions + // but you may want something which is private and visible in actions + // e.g. because you have custom code you want to run in the accessors + private?: boolean | PrivateOptions; sensitive?: boolean; graphqlName?: string; index?: boolean;