Created
March 25, 2021 15:32
-
-
Save hos/20593b62a9c49bc5a8a6b5629855ee0b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { Plugin } from "graphile-build"; | |
| import { | |
| PgAttribute, | |
| PgClass, | |
| PgIntrospectionResultsByKind, | |
| } from "graphile-build-pg"; | |
| import { | |
| embed, | |
| gql, | |
| makeExtendSchemaPlugin, | |
| makePluginByCombiningPlugins, | |
| } from "graphile-utils"; | |
| interface FindResult { | |
| translatableTables: PgClass[]; | |
| translatableColumns: PgAttribute[]; | |
| } | |
| const ResourceEnumName = "TranslatableResourceType"; | |
| const translationsTableName = "translations"; | |
| const TranslationPlugin: Plugin = function TranslationPlugin(builder) { | |
| builder.hook("inflection", (inflection) => { | |
| return { | |
| ...inflection, | |
| translationTypeName(table: PgClass) { | |
| return `${table.name}-translation`; | |
| }, | |
| }; | |
| }); | |
| builder.hook("build", (build) => { | |
| const { inflection, graphql } = build; | |
| const introspection = build.pgIntrospectionResultsByKind as PgIntrospectionResultsByKind; | |
| const { GraphQLObjectType } = graphql; | |
| const findResult: FindResult = introspection.attribute.reduce<FindResult>( | |
| (data, attribute) => { | |
| if (attribute.tags.localize) { | |
| data.translatableColumns.push(attribute); | |
| const table = attribute.class; | |
| if (!data.translatableTables.includes(table)) { | |
| data.translatableTables.push(table); | |
| build.newWithHooks( | |
| GraphQLObjectType, | |
| { | |
| name: inflection.translationTypeName(table), | |
| description: build.wrapDescription( | |
| `Translation type for \`"${inflection.tableType(table)}"\``, | |
| "type" | |
| ), | |
| fields: () => {}, | |
| }, | |
| { pgIntrospection: table, isTranslation: true } | |
| ); | |
| } | |
| } | |
| return data; | |
| }, | |
| { translatableColumns: [], translatableTables: [] } | |
| ); | |
| const { translatableColumns, translatableTables } = findResult; | |
| build.newWithHooks( | |
| build.graphql.GraphQLEnumType, | |
| { | |
| name: ResourceEnumName, | |
| values: { | |
| ...translatableTables.reduce((all, table) => { | |
| const name = inflection.enumName( | |
| inflection.singularize(table.name) | |
| ); | |
| all[name] = { value: table.name }; | |
| return all; | |
| }, {}), | |
| }, | |
| }, | |
| { | |
| isResourceTypeEnum: true, | |
| } | |
| ); | |
| // const findIntrospectionByGraphql | |
| return build.extend(build, { | |
| translatableColumns, | |
| translatableTables, | |
| }); | |
| }); | |
| // Update resource types on types | |
| builder.hook("GraphQLObjectType:fields", (typeConfig, build, context) => { | |
| const { scope } = context; | |
| const { graphql } = build; | |
| const { isNonNullType, GraphQLNonNull } = graphql; | |
| const introspection = scope.pgIntrospection; | |
| if ( | |
| !introspection || | |
| !scope.isPgRowType || | |
| introspection.name !== translationsTableName | |
| ) { | |
| return typeConfig; | |
| } | |
| const enumType = build.getTypeByName(ResourceEnumName); | |
| const isNotNull = isNonNullType(typeConfig.resourceType.type); | |
| const newType = isNotNull ? new GraphQLNonNull(enumType) : enumType; | |
| return { | |
| ...typeConfig, | |
| resourceType: { ...typeConfig.resourceType, type: newType }, | |
| }; | |
| }); | |
| // Update resource types on inputs | |
| builder.hook( | |
| "GraphQLInputObjectType:fields", | |
| (typeConfig, build, context) => { | |
| const { scope } = context; | |
| const { graphql } = build; | |
| const { GraphQLNonNull, isNonNullType } = graphql; | |
| const introspection = scope.pgIntrospection; | |
| if ( | |
| !introspection || | |
| introspection.name !== translationsTableName || | |
| !typeConfig.resourceType | |
| ) { | |
| return typeConfig; | |
| } | |
| const enumType = build.getTypeByName(ResourceEnumName); | |
| const isNotNull = isNonNullType(typeConfig.resourceType.type); | |
| const newType = isNotNull ? new GraphQLNonNull(enumType) : enumType; | |
| return { | |
| ...typeConfig, | |
| resourceType: { | |
| ...typeConfig.resourceType, | |
| type: newType, | |
| }, | |
| }; | |
| } | |
| ); | |
| }; | |
| TranslationPlugin.displayName = "TranslationPlugin"; | |
| const TranslatableSchemas = makeExtendSchemaPlugin((build) => { | |
| const { inflection, pgSql: sql } = build; | |
| const translatableTables = build.translatableTables as FindResult["translatableTables"]; | |
| const typeDefs = translatableTables.map( | |
| (tb) => gql` | |
| extend type ${inflection.tableType(tb)} { | |
| translations: [Translation!]! @pgQuery( | |
| source: ${embed( | |
| sql.fragment`app_public.${sql.identifier(translationsTableName)}` | |
| )} | |
| withQueryBuilder: ${embed((queryBuilder) => { | |
| queryBuilder.where( | |
| sql.fragment`${queryBuilder.getTableAlias()}.resource_id = ${queryBuilder.parentQueryBuilder.getTableAlias()}.id` | |
| ); | |
| queryBuilder.where( | |
| sql.fragment`${queryBuilder.getTableAlias()}.resource_type = ${sql.literal( | |
| tb.name | |
| )}` | |
| ); | |
| })} | |
| ) | |
| }` | |
| ); | |
| return { typeDefs }; | |
| }, "TranslatableSchemas"); | |
| interface ITranslationInput { | |
| locale: string; | |
| key: string; | |
| value: string; | |
| translatableContentDigest: string; | |
| } | |
| const TranslationsRegisterPlugin = makeExtendSchemaPlugin((build) => { | |
| const translatableTables = build.translatableTables as FindResult["translatableTables"]; | |
| return { | |
| typeDefs: gql` | |
| type TranslationsRegisterPayload { | |
| translations: [Translation!] | |
| } | |
| input ATranslationInput { | |
| # The locale of the translation. | |
| locale: String! | |
| # The key of the translation. | |
| key: String! | |
| # Translation value. | |
| value: String! | |
| } | |
| extend type Mutation { | |
| translationsRegister( | |
| # The node ID of the resource. | |
| resourceId: ID! | |
| translations: [ATranslationInput!]! | |
| ): TranslationsRegisterPayload! | |
| } | |
| `, | |
| resolvers: { | |
| Mutation: { | |
| async translationsRegister(_source, args, context, info) { | |
| const { graphile } = info; | |
| const { pgClient } = context; | |
| const { | |
| getTypeAndIdentifiersFromNodeId, | |
| inflection, | |
| } = graphile.build; | |
| const { resourceId, translations } = args as { | |
| resourceId: string; | |
| translations: ITranslationInput[]; | |
| }; | |
| const params: any[] = []; | |
| let query = translations.reduce((_query, trans, index, arr) => { | |
| const { Type, identifiers } = getTypeAndIdentifiersFromNodeId( | |
| resourceId | |
| ); | |
| if (identifiers.length !== 1) { | |
| throw new Error( | |
| `The type "${Type.name}" is not supported for translations, as it have multiple identifiers.` | |
| ); | |
| } | |
| const table = translatableTables.find((table) => { | |
| return inflection.tableType(table) === Type.name; | |
| }); | |
| if (!table) { | |
| throw new Error( | |
| `Invalid type "${Type.name}" for translations, please make sure it have translatable resources.` | |
| ); | |
| } | |
| params.push(identifiers[0], Type.name, table.name, trans.value); | |
| return ( | |
| _query + | |
| `($${index + 1}, $${index + 2}, $${index + 3}, $${index + 4})` + | |
| (index !== arr.length - 1 ? ",\n" : "") | |
| ); | |
| }, `insert into app_public.${translationsTableName}(resource_id, resource_type, key, value) values `); | |
| query += | |
| " on conflict (resource_id, resource_type, key) do update set value = EXCLUDED.value"; | |
| query += " returning *"; | |
| const { rows } = await pgClient.query(query, params); | |
| debugger; | |
| return { translations: rows, query: build.$$isQuery }; | |
| }, | |
| }, | |
| }, | |
| }; | |
| }, "TranslationsRegister"); | |
| const TranslationsRemovePlugin = makeExtendSchemaPlugin((_build) => { | |
| return { | |
| typeDefs: gql` | |
| type TranslationsRemovePayload { | |
| translations: [Translation!]! | |
| } | |
| extend type Mutation { | |
| translationsRemove( | |
| resourceId: ID! | |
| # ID of a translatable resource. | |
| translationKeys: [String!]! | |
| # List of translation keys. | |
| locales: [String!]! # List of translation locales. | |
| ): TranslationsRemovePayload! | |
| } | |
| `, | |
| resolvers: {}, | |
| }; | |
| }, "TranslationsRemove"); | |
| export default makePluginByCombiningPlugins( | |
| TranslationPlugin, | |
| TranslatableSchemas, | |
| TranslationsRegisterPlugin, | |
| TranslationsRemovePlugin | |
| ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment