diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..4aa6ecc0 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +./.github/ @recrwplay diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1e63f2a9..198fda0e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -14,3 +14,6 @@ updates: dependency-type: "production" dev-dependencies: dependency-type: "development" + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-minor", "version-update:semver-patch"] diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index 02bdd80d..e16e28c1 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -9,7 +9,7 @@ on: branches: - "main" - "dev" - + jobs: # note that the build job requires a build-verify script in package.json diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index 7cc9c01d..9ffcedcc 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -64,6 +64,7 @@ *** xref:ogm/directives.adoc[] *** xref:ogm/selection-set.adoc[] *** xref:ogm/type-generation.adoc[] +*** xref:ogm/subscriptions.adoc[] *** xref:ogm/reference.adoc[] ** xref:driver-configuration.adoc[] diff --git a/modules/ROOT/pages/authentication-and-authorization/configuration.adoc b/modules/ROOT/pages/authentication-and-authorization/configuration.adoc index ac285fe2..c392d997 100644 --- a/modules/ROOT/pages/authentication-and-authorization/configuration.adoc +++ b/modules/ROOT/pages/authentication-and-authorization/configuration.adoc @@ -62,6 +62,7 @@ new Neo4jGraphQL({ }); ---- +[[authentication-and-authorization-jwt]] == JWT By default, filtering is available on https://www.rfc-editor.org/rfc/rfc7519#section-4.1[the registered claim names] in the JWT specification. diff --git a/modules/ROOT/pages/getting-started/index.adoc b/modules/ROOT/pages/getting-started/index.adoc index 9af02117..b9e1c89e 100644 --- a/modules/ROOT/pages/getting-started/index.adoc +++ b/modules/ROOT/pages/getting-started/index.adoc @@ -108,12 +108,12 @@ image::neo4j-aura-dashboard.png[width=500] === Using a Neo4j database -For a database located at the default "bolt://localhost:7687" (see more about https://neo4j.com/docs/operations-manual/current/configuration/ports[port configuration]), with the username "neo4j" and the password "password", add the following to the bottom of your `index.js` file: +For a database located at the default "neo4j://localhost:7687" (see more about https://neo4j.com/docs/operations-manual/current/configuration/ports[port configuration]), with the username "neo4j" and the password "password", add the following to the bottom of your `index.js` file: [source, javascript, indent=0] ---- const driver = neo4j.driver( - "bolt://localhost:7687", + "neo4j://localhost:7687", neo4j.auth.basic("username", "password") ); @@ -131,7 +131,6 @@ const server = new ApolloServer({ }); const { url } = await startStandaloneServer(server, { - context: async ({ req }) => ({ req }), listen: { port: 4000 }, }); @@ -179,7 +178,7 @@ const typeDefs = `#graphql `; const driver = neo4j.driver( - "bolt://localhost:7687", + "neo4j://localhost:7687", neo4j.auth.basic("username", "password") ); diff --git a/modules/ROOT/pages/ogm/reference.adoc b/modules/ROOT/pages/ogm/reference.adoc index a2abb868..4c0df3ea 100644 --- a/modules/ROOT/pages/ogm/reference.adoc +++ b/modules/ROOT/pages/ogm/reference.adoc @@ -16,7 +16,7 @@ The following sections work as a reference guide to all functionalities in OGM a |`constructor` |Returns an `OGM` instance. -Takes an `input` object as a parameter, which is then passed to the `Neo4jGraphQL` constructor. Supported options are listed in the documentation for xref::reference/api-reference/neo4jgraphql.adoc[`Neo4jGraphQL`]. +Takes an `input` object as a parameter, which is then passed to the `Neo4jGraphQL` constructor. a| [source, javascript, indent=0] ---- @@ -27,7 +27,7 @@ const ogm = new OGM({ |`init` |Asynchronous method to initialize the OGM. -Internally, calls xref::reference/api-reference/neo4jgraphql.adoc#api-reference-getschema[`Neo4jGraphQL.getSchema()`] to generate a GraphQL schema, and stores the result. +Internally, calls `Neo4jGraphQL.getSchema()` to generate a GraphQL schema, and stores the result. Initializes any models which have been created before this execution, and will throw an error if any of them are invalid. a| [source, javascript, indent=0] diff --git a/modules/ROOT/pages/ogm/subscriptions.adoc b/modules/ROOT/pages/ogm/subscriptions.adoc new file mode 100644 index 00000000..bca80407 --- /dev/null +++ b/modules/ROOT/pages/ogm/subscriptions.adoc @@ -0,0 +1,247 @@ +[[ogm-subscriptions]] +:description: This how-to guide shows how to use the OGM with subscriptions. += How to use the OGM with subscriptions + +The Neo4j GraphQL Library can be used in conjunction with the OGM in order to extend the library's functionalities, or to take advantage of the xref:ogm/private.adoc[`@private` directive]. + +== Usage + +This section shows how to use subscriptions with the OGM inside custom resolvers. + +=== Prerequisites + +. Use the following type definitions: +[source, javascript, indent=0] +---- +const typeDefs = `#graphql + type User { + email: String! + password: String! @private + } +`; +---- + +. Set up a subscriptions plugin instance to be shared between the Library and the OGM constructors: +[source, javascript, indent=0] +---- +const subscriptionsPlugin = new Neo4jGraphQLSubscriptionsSingleInstancePlugin(); +---- + +. Set up a server that supports subscriptions. +See more instructions in the xref:subscriptions/getting-started.adoc#setting-up-server[Getting started page]. + + +=== Adding the OGM + +In order to utilize the `@private` marked field on the `User` type, you need to create the `User` model. +Also, to use the subscriptions, you need to pass in the subscriptions plugin instance to the OGM constructor: + +[source, javascript, indent=0] +---- +const ogm = new OGM({ typeDefs, driver, plugins: { subscriptions: subscriptionsPlugin } }); +const Profile = ogm.model("Profile"); +---- + +Make sure you initialize the OGM instance before using it by adding the following line to the `main()` function: +[source, javascript, indent=0] +---- +await ogm.init(); +---- + +=== Adding a custom resolver + +xref:custom-resolvers/[Custom resolvers] can be used for multiple reasons such as performing data manipulation and checks, or interact with third party systems. +In this case, you only need to create a `User` node with the `password` field set. +You can do that by adding a sign-up mutation. +For example: + +[source, javascript, indent=0] +---- +const resolvers = { + Mutation: { + signUp: async (_source, { username, password }) => { + const [existing] = await User.find({ + where: { + username, + }, + }); + if (existing) { + throw new Error(`User with username ${username} already exists!`); + } + const { users } = await User.create({ + input: [ + { + username, + password, + } + ] + }); + return createJWT({ sub: users[0].id }); + }, + }, +}; +const typeDefs = ` + type Mutation { + signUp(username: String!, password: String!): String! + } +` +---- + +[discrete] +=== Conclusion + +Altogether, it should look like this: + +[source, javascript, indent=0] +---- +const subscriptionsPlugin = new Neo4jGraphQLSubscriptionsSingleInstancePlugin(); +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); +const typeDefs = ` + type User { + email: String! + password: String! @private + } + type Mutation { + signUp(username: String!, password: String!): String! # custom resolver + } +` +const resolvers = { + Mutation: { + signUp: async (_source, { username, password }) => { + const [existing] = await User.find({ + where: { + username, + }, + }); + if (existing) { + throw new Error(`User with username ${username} already exists!`); + } + const { users } = await User.create({ + input: [ + { + username, + password, + } + ] + }); + return createJWT({ sub: users[0].id }); + }, + }, +}; +// Share the subscriptions plugin instance across the Library and the OGM +const neoSchema = new Neo4jGraphQL({ + typeDefs, + driver, + resolvers, + plugins: { + subscriptions: subscriptionsPlugin, + }, +}); +const ogm = new OGM({ typeDefs, driver, plugins: { subscriptions: subscriptionsPlugin } }); +const Profile = ogm.model("Profile"); + +async function main() { + // initialize the OGM instance + await ogm.init(); + + // Apollo server setup with WebSockets + const app = express(); + const httpServer = createServer(app); + const wsServer = new WebSocketServer({ + server: httpServer, + path: "/graphql", + }); + + // Neo4j schema + const schema = await neoSchema.getSchema(); + + const serverCleanup = useServer( + { + schema, + context: (ctx) => { + return ctx; + }, + }, + wsServer + ); + + const server = new ApolloServer({ + schema, + plugins: [ + ApolloServerPluginDrainHttpServer({ + httpServer, + }), + { + async serverWillStart() { + return Promise.resolve({ + async drainServer() { + await serverCleanup.dispose(); + }, + }); + }, + }, + ], + }); + await server.start(); + + app.use( + "/graphql", + cors(), + bodyParser.json(), + expressMiddleware(server, { + context: async ({ req }) => ({ req }), + }) + ); + + const PORT = 4000; + httpServer.listen(PORT, () => { + console.log(`Server is now running on http://localhost:${PORT}/graphql`); + }); +} +---- + + +== Receiving the subscription events + +First, run the following subscription to receive `User` creation events: +[source, gql, indent=0] +---- +subscription { + userCreated { + createdUser { + email + } + event + } +} +---- + +Then run the sign-up mutation: +[source, gql, indent=0] +---- +mutation { + signUp(email: "jon.doe@xyz.com", password: "jondoe") { + email + password + } +} +---- + +The results should look like this: +[source, gql, indent=0] +---- +{ + "data": { + "userCreated": { + "createdUser": { + "email": "jon.doe@xyz.com", + "password": "jondoe" + }, + "event": "CREATE" + } + } +} +---- diff --git a/modules/ROOT/pages/subscriptions/getting-started.adoc b/modules/ROOT/pages/subscriptions/getting-started.adoc index a31578ac..137eaf6e 100644 --- a/modules/ROOT/pages/subscriptions/getting-started.adoc +++ b/modules/ROOT/pages/subscriptions/getting-started.adoc @@ -33,6 +33,7 @@ Then, the next step is to install the following dependencies: npm i --save ws graphql-ws neo4j-driver @neo4j/graphql express @apollo/server body-parser cors ---- +[setting-up-server] == Set up an `@apollo/server` server Add the following code to your `index.js` file to implement a simple `@apollo/server` server with subscriptions (for more options, see link:https://www.apollographql.com/docs/apollo-server/data/subscriptions/[Apollo's documentation]): diff --git a/modules/ROOT/pages/type-definitions/directives/index.adoc b/modules/ROOT/pages/type-definitions/directives/index.adoc index a12a02d9..3436b145 100644 --- a/modules/ROOT/pages/type-definitions/directives/index.adoc +++ b/modules/ROOT/pages/type-definitions/directives/index.adoc @@ -42,6 +42,9 @@ of any required fields that is passed as arguments to the custom resolver. | xref::/type-definitions/directives/autogeneration.adoc#type-definitions-autogeneration-id[`@id`] | Marks a field as the unique ID for an object type, and allows for autogeneration of IDs. +| xref::/authentication-and-authorization/configuration.adoc#authentication-and-authorization-jwt[`@jwtClaim`] +| Marks a field as the unique ID for an object type, and allows for autogeneration of IDs. + | xref::/type-definitions/directives/default-values.adoc#type-definitions-default-values-limit[`@limit`] | Used on nodes to inject values into Cypher `LIMIT` clauses. diff --git a/modules/ROOT/pages/type-definitions/directives/indexes-and-constraints.adoc b/modules/ROOT/pages/type-definitions/directives/indexes-and-constraints.adoc index ec9b3bc5..7b19d80b 100644 --- a/modules/ROOT/pages/type-definitions/directives/indexes-and-constraints.adoc +++ b/modules/ROOT/pages/type-definitions/directives/indexes-and-constraints.adoc @@ -225,7 +225,7 @@ query { == Asserting constraints -In order to ensure that the specified constraints exist in the database, you need to run the function `assertIndexesAndConstraints` (see more details in xref::reference/api-reference/neo4jgraphql.adoc#api-reference-assertconstraints[API reference]). +In order to ensure that the specified constraints exist in the database, you need to run the function `assertIndexesAndConstraints`. A simple example to create the necessary constraints might look like the following, assuming a valid driver instance in the variable `driver`. This creates two constraints, one for each field decorated with `@id` and `@unique`, and apply the indexes specified in `@fulltext`: diff --git a/modules/ROOT/pages/type-definitions/types/interfaces.adoc b/modules/ROOT/pages/type-definitions/types/interfaces.adoc index a85af4cb..c9de4abf 100644 --- a/modules/ROOT/pages/type-definitions/types/interfaces.adoc +++ b/modules/ROOT/pages/type-definitions/types/interfaces.adoc @@ -198,4 +198,4 @@ query GetProductionsStartingWithThe { } } } ----- +---- \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 477325ce..d542f8a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3015,4 +3015,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 43acf3ed..19054c18 100644 --- a/package.json +++ b/package.json @@ -39,4 +39,4 @@ "glob-parent": "6.0.2" } } -} +} \ No newline at end of file