Neo4j Graphql Error Implementing Interface

123 views Asked by At

I have a graphql interface like this

interface Verifiable {
    verifications: [Verification!]!
}

I then implement that interface like this:

type Address implements Verifiable  @node(labels: ["Address", "Verifiable"]) @key(fields:"id"){
  id: ID! @id
  street: String
  suburb: String
  city: String
  stateProvince: String
  country: String
  verifications: [Verification!]! @relationship(type: "VERIFIED", direction: OUT)
}

On running the project, I am getting this error:

Apr-12-2023 21:16:52    error   GQL API        The schema is not a valid GraphQL schema.. Caused by:
[<unnamed>] Interface field VerifiableEventPayload.verifications expected but AddressEventPayload does not provide it.

GraphQL request:582:3
581 | interface VerifiableEventPayload {
582 |   verifications: [Verification!]!
    |   ^
583 | }

GraphQL request:712:1
711 |
712 | type AddressEventPayload implements VerifiableEventPayload {
    | ^
713 |   id: ID!

Implementing my graphql server with apollo server v4. The error however occurs at getting the neo4j schema before I instantiate the apollo server.

My package.json dependencies:

"dependencies": {
    "@apollo/server": "^4.5.0",
    "@apollo/subgraph": "^2.3.4",
    "@escape.tech/graphql-armor": "1.8.1",
    "@graphql-tools/load": "^7.8.13",
    "@graphql-tools/load-files": "^6.6.1",
    "@graphql-tools/merge": "^8.4.0",
    "@graphql-tools/schema": "^9.0.17",
    "@graphql-tools/utils": "^9.2.1",
    "@neo4j/cypher-builder": "^0.2.1",
    "@neo4j/graphql": "^3.17.1",
    "@neo4j/graphql-ogm": "^3.17.1",
    "@neo4j/graphql-plugin-auth": "^2.1.0",
    "@neo4j/introspector": "^1.0.3",
    "bcrypt": "^5.1.0",
    "cookie-parser": "^1.4.6",
    "cors": "^2.8.5",
    "dotenv": "^16.0.3",
    "express": "^4.18.2",
    "graphql": "^16.6.0",
    "graphql-tag": "^2.12.6",
    "jsonwebtoken": "^9.0.0",
    "lodash": "^4.17.21",
    "neo4j-driver": "^5.6.0",
    "nodemailer": "^6.9.1",
    "winston": "^3.8.2"
  }

Here is the index file where I am setting up the server environment.

import express, {Express} from 'express';
import {CypherOperatorEngine, Neo4jGraphQL, Neo4jGraphQLSubscriptionsSingleInstancePlugin} from '@neo4j/graphql';
import { Neo4jGraphQLAuthJWTPlugin } from '@neo4j/graphql-plugin-auth';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import { ApolloServer } from '@apollo/server';
import neo4jDriver from 'neo4j-driver';
import { OGM } from '@neo4j/graphql-ogm';
import dotenv from 'dotenv';
import {expressMiddleware} from '@apollo/server/express4'
import http from 'http';
import cors from 'cors'; 
import resolvers from './graphql/resolvers/index.js';
import typeDefs from './graphql/schema/types/index.js';
import { BayGraphQLServerContext, IAuthSubject } from './app.d.js';
import BayRegistryAuthenticationPlugin from './middleware/authentication.js';
import {logger} from './middleware/logging.js';
import apolloArmorProtection from "./middleware/armor.js";
import { BayJwt } from './services/utils/security/jwt.js';
import { Server, WebSocket, WebSocketServer } from 'ws';
import { useServer } from "graphql-ws/lib/use/ws"

dotenv.config();

logger.info(`${(new Date()).getFullYear()} Some Company (Pvt) Limited.`);
logger.info("GQL API v0.0.1 ");

const driver = neo4jDriver.driver(
    process.env.NEO4J_DATABASE_URI="bolt://127.0.0.1:7687",
    neo4jDriver.auth.basic(
        process.env.NEO4J_DATABASE_USERID="neo4j", 
        process.env.NEO4J_DATABASE_SECRET="graphista"
    )
);

const ogm = new OGM({typeDefs, resolvers});

const app:Express = express();

const keyPublic="";

app.use(express.json());

app.use(express.urlencoded({extended: true}));

const httpServer = http.createServer(app);

const neo4jGraphql = new Neo4jGraphQL({
    typeDefs,
    driver,
    resolvers,
    config:{
        driverConfig:{
            database: process.env.NEO4J_DATABASE
        },
    },
    plugins:{
        subscriptions: new Neo4jGraphQLSubscriptionsSingleInstancePlugin(),
        auth: new Neo4jGraphQLAuthJWTPlugin({
            secret: keyPublic,
            globalAuthentication: false,
        }),
    },
    features:{
        populatedBy:{
            callbacks:{
                copyright: (parent, args, context)=>"Some Company Private Limited"
            }
        }
    }
});

const webSocketServer = new WebSocketServer({
    server: httpServer,
    path: "api/v1/gql"
});


Promise.all([
    neo4jGraphql.getSubgraphSchema(),
    neo4jGraphql.assertIndexesAndConstraints({
        options:{
            create: true
        }
    }),
    //TODO had to manually change the @neo4j/graphql-ogm OGM.js to call neoSchema.getSubgraphSchema() instead of neoSchema.getSchema
    //ogm.init()
]).then(([schema])=>{
    logger.info("Neo subgraph schema generated, instantiating apollo server");

    const serverCleanup = useServer({schema}, webSocketServer);

    const apolloServer = new ApolloServer<BayGraphQLServerContext>({
        schema,
        csrfPrevention: true,
        introspection: true,
        //...apolloArmorProtection,//@escape.tech/graphql armour protection
        plugins:[
            //...apolloArmorProtection.plugins,
            ApolloServerPluginDrainHttpServer({httpServer}),
            BayRegistryAuthenticationPlugin(),
            {
                async serverWillStart(){
                    logger.info("Apollo server starting inline plugin");
                    return {
                        async drainServer(){
                            await serverCleanup.dispose();
                        }
                    }
                }
            }
        ]
    });
    apolloServer.start().then(()=>{
        app.use(
            process.env.GRAPHQL_API_PATH="/api/:version/gql",
            cors<cors.CorsRequest>(),
            //logger,
            expressMiddleware(apolloServer, {
                context: async ({req, res}) =>{
                    let user: IAuthSubject={userUUID:null};
                    let jwt: IAuthSubject = {userUUID: null};
                    await BayJwt.JwtDecode(req, req.headers?.authorization?.split(" ")[1] || "")
                        .then(decoded=>{
                            logger.info(`Generating apollo context, JWT OK!`);
                            user=decoded;
                            BayJwt.JwtLoadProfile(decoded).then(profiled=>{
                                user = profiled;
                            });
                    }).catch(error=>{
                        logger.error(error.message);
                    });
                    return {
                        req,res,user, jwt: user ? user : "", ogm,
                        cypherParams:{
                            company: "some company pvt limited"
                        }
                    }   
                },
            })
        )
    });
})
.catch(error=>{
    logger.error("Error generating neo4j schema and asserting constraints");
    logger.error(error.message);
    logger.error(JSON.stringify(error));
});

httpServer.listen(
    {
        port: process.env.GRAPHQL_API_PORT
    },
    ()=>{
        logger.info(`API started at ${process.env.GRAPHQL_API_PORT}`)
    }
);

The type definitions that are implementing interfaces and related interfaces are as below:

interface IVerifiable {
    #verifications: [Verification!]!
    isVerifiable: Boolean @default(value: false)
}

interface IValuable {
  valuations: [Valuation!]!
  isValuable: Boolean @default(value: false)
}

interface IReviewable{
  #reviews: [Persona!]!
  isReviewable: Boolean @default(value: false)
}

interface IOwnable {
  #ownerships: [Ownership!]!
  isOwnable: Boolean @default(value: false)
}

interface IPrivacy{
  #privateFields: [String!]!
  #privateFields3rdPartyAccessMode: PRIVATE_FIELD_3RD_PARTY_ACCESS_MODE!
  isConfidential: Boolean @default(value: false)
}

type Identity implements IVerifiable @node(labels: ["Identity", "Verifiable"]) @key(fields:"id"){
  id: ID! @id @readonly
  identity: String!
  type: String!
  identityUri: String
  subject: Persona @relationship(type: "IDENTIFIED_BY", direction: IN)
  issuer: Persona @relationship(type: "ISSUED_BY", direction: OUT)
  isVerifiable: Boolean!
  verifications: [Verification!]! @relationship(type: "WAS_VERIFIED", direction: OUT)
  issueDate: Date
  expiryDate: Date
  description: String
  comments: String
}


type Address implements IVerifiable  @node(labels: ["Address", "Verifiable"]) @key(fields:"id"){
  id: ID! @id @readonly
  suite: String
  street: String
  suburb: String
  city: String
  stateProvince: String
  country: String
  geoLocation: Point
  geoBoundary: [Point!]
  isResidential: Boolean @default(value: true)
  domicili: [Domicilium!]! @relationship(type: "IS_DOMICILIUM", direction: OUT)
  verifications: [Verification!]! @relationship(type: "WAS_VERIFIED", direction: OUT)
  isVerifiable: Boolean! @default(value: true)
}

type Domicilium implements IVerifiable @node(labels: ["Domicilium", "Residence", "Verifiable"]) @key(fields: "domiciliumId"){
  domiciliumId: ID! @id @readonly
  address: Address! @relationship(type: "IS_DOMICILIUM", direction: IN)
  resident: Persona! @relationship(type: "HAS_DOMICILIUM", direction: IN)
  startDate: Date
  stopDate: Date
  owned: Boolean @default(value: true)
  proofOfDomiciliumUri: String
  verifications: [Verification!]! @relationship(type: "VERIFIED", direction: OUT)
  isVerifiable: Boolean! @default(value: true)
}

type Ownership implements IVerifiable @node(labels: ["Ownership", "Verifiable"]) @key(fields: "ownershipId"){
    ownershipId: ID! @id @readonly
    predecessor: Ownership @relationship(type: "CHANGE_OF_OWNERSHIP", direction: IN)
    owners: [Persona!]! @relationship(type: "HAS_OWNERSHIP", direction: IN)
    asset: Asset! @relationship(type: "OWNERSHIP_OF", direction: OUT)
    verifications: [Verification!]! @relationship(type: "VERIFIED", direction: OUT)
    isVerifiable: Boolean!
    startDate: Date
    stopDate: Date
}
"""
  Valuation of a IValuable item. Self valuation is allowed
"""
type Valuation implements IVerifiable @node(labels: ["Valuation"]) @key(fields: "valuationId"){
  valuationId: ID! @id @readonly
  selfValuation: Boolean! @default(value: true)
  desktopValuation: Boolean! @default(value: true)
  valuer: Persona @relationship(type: "DID_A_VALUATION", direction: IN)
  verifications: [Verification!]! @relationship(type: "VERIFIED", direction: OUT)
  isVerifiable: Boolean!
  value: Float!
  asset: Asset! @relationship(type: "WAS_VALUED", direction: IN)
  valuationDate: DateTime
  isValuable: Boolean!
  expiryDate: DateTime
}


type Asset implements IAsset & IVerifiable & IOwnable & IValuable @node(labels:["Asset", "Verifiable", "Ownable", "Valuable"]) @key(fields: "assetId"){
  assetId: ID! @id @readonly
  isValuable: Boolean!
  verifications: [Verification!]! @relationship(type: "VERIFIED", direction: OUT)
  isVerifiable: Boolean!
  valuations: [Valuation!]! @relationship(type: "VALUED", direction: OUT)
  ownerships: [Ownership!]! @relationship(type: "OWNED", direction: IN)
  isAsset: Boolean!
  isOwnable: Boolean!
  specification: String!
  startDate: DateTime @timestamp(operations: [CREATE])
}

Any ideas what i am getting wrong?

Thanks in advance.

0

There are 0 answers