Skip to content

Commit

Permalink
feat: unauthenticated experience with auto load from files
Browse files Browse the repository at this point in the history
  • Loading branch information
samjcombs committed Nov 21, 2024
1 parent 2045234 commit 9f3f29b
Show file tree
Hide file tree
Showing 29 changed files with 348 additions and 224 deletions.
2 changes: 0 additions & 2 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
frontend/node_modules
frontend/package-lock.json
frontend/build

backend/node_modules
backend/package-lock.json
backend/dist
backend/instant-mock.db
backend/data
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ ARG REACT_APP_FRONTEND_PORT
ENV REACT_APP_FRONTEND_PORT=$REACT_APP_FRONTEND_PORT

COPY frontend/package*.json ./
RUN npm install
RUN npm ci

COPY frontend/ .
RUN npm run build
Expand All @@ -21,7 +21,7 @@ FROM node:20-alpine AS backend-builder
WORKDIR /app/backend

COPY backend/package*.json ./
RUN npm install
RUN npm ci

COPY backend/ .
RUN npm run build
Expand Down
11 changes: 10 additions & 1 deletion backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
"main": "dist/server.js",
"scripts": {
"build": "npx tsc && npm run copy-graphql",
"copy-graphql": "cp -R src/**/*.graphql dist/graphql/",
"codegen:apollo": "graphql-codegen --config codegen.apollo.ts",
"copy-graphql": "cp -R src/**/*.graphql dist/graphql/",
"db:reset": "ts-node src/scripts/reset-database.ts",
"debug": "kill -9 $(lsof -t -i :9229) 2>/dev/null || true && DEBUGGING=true node --inspect-brk=9229 -r ts-node/register src/server.ts & pid=$! && sleep 2 && open -a 'Google Chrome' 'chrome://inspect' && wait $pid",
"dev": "npm run db:reset && ts-node-dev --respawn --transpile-only src/server.ts",
"generate:key": "ts-node src/scripts/generate-encryption-key.ts",
"lint": "eslint .",
"lint-fix": "eslint . --fix",
"migrate:create": "npx mikro-orm migration:create --config src/mikro-orm.sqlite.ts",
Expand All @@ -16,9 +19,7 @@
"seed:create": "npx mikro-orm seeder:create --config src/mikro-orm.sqlite.ts",
"seed:run": "npx mikro-orm seeder:run --config src/mikro-orm.sqlite.ts",
"start": "node dist/server.js",
"test": "jest",
"generate:key": "ts-node src/scripts/generate-encryption-key.ts",
"db:reset": "ts-node src/scripts/reset-database.ts"
"test": "jest"
},
"dependencies": {
"@apollo/client": "3.11.8",
Expand All @@ -44,6 +45,7 @@
"dotenv": "^16.4.5",
"express": "^4.17.1",
"express-http-proxy": "^2.0.0",
"figlet": "^1.8.0",
"get-port": "^7.1.0",
"global-agent": "^3.0.0",
"graphql": "^16.9.0",
Expand All @@ -68,6 +70,7 @@
"@mikro-orm/cli": "^6.3.13",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/figlet": "^1.7.0",
"@types/global-agent": "^2.1.3",
"@types/jest": "^29.5.13",
"@types/lodash": "^4.17.7",
Expand Down
2 changes: 1 addition & 1 deletion backend/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const config = convict({
frontendPort: {
env: 'FRONTEND_PORT',
format: 'port',
default: 3033,
default: 3032,
},
},
supertokens: {
Expand Down
2 changes: 1 addition & 1 deletion backend/src/config/supertokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ function getConfiguredProviders() {
}

export const SuperTokensConfig: TypeInput = {
debug: true,
// debug: true,
supertokens: {
connectionURI: config.get('supertokens.connectionUri'),
},
Expand Down
167 changes: 96 additions & 71 deletions backend/src/graphql/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,27 @@ export default class Client {

constructor() {
this.schemaLoader = new SchemaLoader();
this.loadLocalSchemas();
}

private async loadLocalSchemas() {
logger.startup('Loading local schemas');
const schemas = await this.schemaLoader.loadFromFiles();
logger.startup('Local schemas loaded', {
count: schemas.length,
schemaNames: schemas.map((s) => s.name),
});
}

private async getGraphsFromFiles(): Promise<
NonNullable<GetGraphsQuery['organization']>['graphs']
> {
logger.debug('Attempting to load schemas from files');
const schemas = await this.schemaLoader.loadFromFiles();
logger.debug('Loaded schemas:', {
count: schemas.length,
schemaNames: schemas.map((s) => s.name),
});

return schemas.map(({name}) => ({
id: `local-${name}`,
Expand Down Expand Up @@ -237,42 +252,47 @@ export default class Client {
async getGraphs(): Promise<
NonNullable<GetGraphsQuery['organization']>['graphs']
> {
if (process.env.USE_LOCAL_SCHEMA === 'true') {
logger.graph('Fetching graphs from local files');
return this.getGraphsFromFiles();
logger.graph('Fetching graphs from local files');
const localGraphs = await this.getGraphsFromFiles();

if (!this.organizationId) {
return localGraphs;
}

logger.graph('Fetching graphs from Apollo', {
organizationId: this.organizationId,
});

const {data} = await this.apolloClient.query<GetGraphsQuery>({
query: GET_GRAPHS,
variables: {organizationId: this.organizationId},
});

if (data.organization?.__typename === 'Organization') {
logger.graph('Successfully retrieved graphs', {
count: data.organization.graphs.length,
try {
const {data} = await this.apolloClient.query<GetGraphsQuery>({
query: GET_GRAPHS,
variables: {organizationId: this.organizationId},
});
return data.organization.graphs;

if (data.organization?.__typename === 'Organization') {
logger.graph('Successfully retrieved graphs', {
count: data.organization.graphs.length,
});
return [...localGraphs, ...data.organization.graphs];
}
} catch (e) {
logger.error('Error fetching Apollo graphs', {error: e});
return localGraphs;
}

logger.error('Failed to retrieve organization graphs', {
organizationId: this.organizationId,
typename: data.organization?.__typename,
});
throw new Error('Unable to retrieve graphs');
}

async getGraph(graphId: string): Promise<GetGraphQuery['graph']> {
if (process.env.USE_LOCAL_SCHEMA === 'true') {
const schemas = await this.schemaLoader.loadFromFiles();
const graphName = graphId.replace('local-', '');
const schema = schemas.find((s) => s.name === graphName);

if (!schema) return null;
// Always check local schemas first
const schemas = await this.schemaLoader.loadFromFiles();
const graphName = graphId.replace('local-', '');
const localSchema = schemas.find((s) => s.name === graphName);

if (localSchema) {
return {
variants: [
{
Expand All @@ -282,7 +302,7 @@ export default class Client {
latestPublication: {
publishedAt: new Date().toISOString(),
schema: {
document: schema.schema,
document: localSchema.schema,
},
},
},
Expand All @@ -293,31 +313,34 @@ export default class Client {
};
}

const res = await this.apolloClient.query<GetGraphQuery>({
query: GET_GRAPH,
variables: {
graphId,
filterBy: {
status: ['APPROVED', 'DRAFT', 'IMPLEMENTED', 'OPEN'],
// If not found locally and we have Apollo client initialized, try remote
if (this.apolloClient) {
const res = await this.apolloClient.query<GetGraphQuery>({
query: GET_GRAPH,
variables: {
graphId,
filterBy: {
status: ['APPROVED', 'DRAFT', 'IMPLEMENTED', 'OPEN'],
},
},
},
});
return res.data.graph;
});
return res.data.graph;
}

return null;
}

async getVariant(
graphId: string,
variantName: string
): Promise<NonNullable<GetVariantQuery['graph']>['variant']> {
if (process.env.USE_LOCAL_SCHEMA === 'true') {
const schemas = await this.schemaLoader.loadFromFiles();
const localId = parseInt(graphId.replace('local-', ''));
const schema = schemas[localId];

if (!schema) return null;
const schemas = await this.schemaLoader.loadFromFiles();
const graphName = graphId.replace('local-', '');
const localSchema = schemas.find((s) => s.name === graphName);

if (localSchema) {
return {
id: `local-${localId}@${variantName}`,
id: `local-${graphName}@${variantName}`,
name: variantName,
url: null,
isProposal: false,
Expand All @@ -327,39 +350,41 @@ export default class Client {
latestPublication: {
publishedAt: new Date().toISOString(),
schema: {
document: schema.schema,
document: localSchema.schema,
},
},
};
}

const {data} = await this.apolloClient.query<GetVariantQuery>({
query: GET_VARIANT,
variables: {graphId, name: variantName},
});
return data.graph?.variant;
if (this.apolloClient) {
const {data} = await this.apolloClient.query<GetVariantQuery>({
query: GET_VARIANT,
variables: {graphId, name: variantName},
});
return data.graph?.variant;
}

return null;
}

async getGraphWithSubgraphs(
graphId: string
): Promise<GetGraphWithSubgraphsQuery['graph']> {
if (process.env.USE_LOCAL_SCHEMA === 'true') {
const schemas = await this.schemaLoader.loadFromFiles();
const localId = parseInt(graphId.replace('local-', ''));
const schema = schemas[localId];

if (!schema) return null;
const schemas = await this.schemaLoader.loadFromFiles();
const graphName = graphId.replace('local-', '');
const localSchema = schemas.find((s) => s.name === graphName);

if (localSchema) {
return {
variants: [
{
key: `local-${localId}@current`,
key: `local-${graphName}@current`,
displayName: 'current',
name: 'current',
latestPublication: {
publishedAt: new Date().toISOString(),
schema: {
document: schema.schema,
document: localSchema.schema,
},
},
},
Expand All @@ -370,31 +395,31 @@ export default class Client {
};
}

const {data} = await this.apolloClient.query<GetGraphWithSubgraphsQuery>({
query: GET_GRAPH_WITH_SUBGRAPHS,
variables: {
graphId,
filterBy: {
status: ['APPROVED', 'DRAFT', 'IMPLEMENTED', 'OPEN'],
if (this.apolloClient) {
const {data} = await this.apolloClient.query<GetGraphWithSubgraphsQuery>({
query: GET_GRAPH_WITH_SUBGRAPHS,
variables: {
graphId,
filterBy: {
status: ['APPROVED', 'DRAFT', 'IMPLEMENTED', 'OPEN'],
},
},
},
});
return data.graph;
});
return data.graph;
}

return null;
}

async getSchema(graphId: string, name: string): Promise<string | null> {
if (process.env.USE_LOCAL_SCHEMA === 'true') {
const schemas = await this.schemaLoader.loadFromFiles();
// TODO we need to think if we want to support multiple schemas loaded from files, for now we only select the first one
// const debugSchema = schemas.map((s) => s.name);
// logger.debug('Found schemas:', {debugSchema});
// const schema = schemas.find(
// (s) => s.name === graphId.replace('local-', '')
// );
const schema = schemas[0].schema;
logger.debug('found schema', {schema});
// return schema ? schema.schema : null;
return schemas[0].schema;
const schemas = await this.schemaLoader.loadFromFiles();
const localSchema = schemas.find(
(s) => s.name === graphId.replace('local-', '')
);

if (localSchema) {
logger.debug('Using local schema', {name: localSchema.name});
return localSchema.schema;
}

try {
Expand Down
Loading

0 comments on commit 9f3f29b

Please sign in to comment.