Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/cli/src/commands/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default async (
const project = new LaunchQLProject(cwd);

project.ensureWorkspace();
project.resetCwd(project.workspacePath);

const options = getEnvOptions();

Expand Down Expand Up @@ -105,13 +106,15 @@ export default async (
}
]);

const outdir = resolve(project.workspacePath, 'packages/');

await exportMigrations({
project,
options,
dbInfo,
author,
schema_names,
outdir: resolve(project.workspacePath, 'packages/'),
outdir,
extensionName,
metaExtensionName
});
Expand Down
83 changes: 77 additions & 6 deletions packages/cli/src/commands/server.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { LaunchQLServer as server } from '@launchql/server';
import { CLIOptions, Inquirerer, Question } from 'inquirerer';
import { CLIOptions, Inquirerer, OptionValue, Question } from 'inquirerer';
import { getEnvOptions, LaunchQLOptions } from '@launchql/types';
import { getRootPgPool, Logger } from '@launchql/server-utils';

const log = new Logger('server');

const questions: Question[] = [
const initialQuestions: Question[] = [
{
name: 'simpleInflection',
message: 'Use simple inflection?',
Expand Down Expand Up @@ -37,6 +37,14 @@ const questions: Question[] = [
required: false,
default: 5555,
useDefault: true
},
{
name: 'useMetaApi',
message: 'Use Meta API for schema discovery?',
type: 'confirm',
required: false,
default: true,
useDefault: true
}
];

Expand Down Expand Up @@ -72,13 +80,14 @@ export default async (
selectedDb = database;
log.info(`📌 Using database: "${selectedDb}"`);
}

const {
oppositeBaseNames,
port,
postgis,
simpleInflection
} = await prompter.prompt(argv, questions);
simpleInflection,
useMetaApi
} = await prompter.prompt(argv, initialQuestions);

const options: LaunchQLOptions = getEnvOptions({
pg: { database: selectedDb },
Expand All @@ -88,10 +97,72 @@ export default async (
postgis
},
server: {
port
port,
middleware: {
useMetaApi
}
}
});

let selectedSchemas: string[] = [];

if (!useMetaApi) {
const appDb = await getRootPgPool({ database: selectedDb });
const result = await appDb.query(`
SELECT nspname AS schema_name
FROM pg_namespace
WHERE nspname NOT LIKE 'pg_%'
AND nspname != 'information_schema'
ORDER BY nspname;
`);

const availableSchemas = result.rows.map(row => row.schema_name);

const { schemas } = await prompter.prompt(argv, [
{
name: 'schemas',
message: 'Select schemas to expose',
type: 'checkbox',
options: availableSchemas,
required: true
}
]);

selectedSchemas = schemas.filter((s:OptionValue)=>s.selected).map((s:OptionValue)=>s.value);

/// roles

const { anonRole, roleName } = await prompter.prompt(argv, [
{
name: 'anonRole',
message: 'Select anonymous role',
type: 'autocomplete',
options: ['postgres', 'anonymous', 'authenticated'],
default: 'anonymous',
required: true
},
{
name: 'roleName',
message: 'Select default role',
type: 'autocomplete',
options: ['postgres', 'anonymous', 'authenticated'],
default: 'authenticated',
required: true
}
]);

options.graphile.anonRole = anonRole;
options.graphile.roleName = roleName;

}

if (!useMetaApi && selectedSchemas.length > 0) {
options.graphile = {
...options.graphile,
schema: selectedSchemas
};
}

log.success('✅ Selected Configuration:');
for (const [key, value] of Object.entries(options)) {
log.debug(`${key}: ${JSON.stringify(value)}`);
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/class/launchql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export interface InitModuleOptions {
}

export class LaunchQLProject {
public readonly cwd: string;
public cwd: string;
public workspacePath?: string;
public modulePath?: string;
public config?: any;
Expand All @@ -81,7 +81,11 @@ export class LaunchQLProject {
private _moduleInfo?: ExtensionInfo;

constructor(cwd: string = process.cwd()) {
this.cwd = path.resolve(cwd);
this.resetCwd(cwd);
}

resetCwd(cwd: string) {
this.cwd = cwd;
this.workspacePath = this.resolveLaunchqlPath();
this.modulePath = this.resolveSqitchPath();

Expand Down
3 changes: 2 additions & 1 deletion packages/server/src/errors/50x.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default `
export default (code: string) => `
<html lang="en">

<head>
Expand Down Expand Up @@ -205,6 +205,7 @@ export default `
<div class="textc">
<h1>Uh Oh!</h1>
<p>We’re really sorry about that. Please contact support of the issue persists.</p>
<p>code: ${code}</p>
</div>
</div>
</article>
Expand Down
3 changes: 2 additions & 1 deletion packages/server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './server';
export * from './server';
export * from './middleware/directSchema';
14 changes: 5 additions & 9 deletions packages/server/src/middleware/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const createApiMiddleware = (opts: LaunchQLOptions) => {
res.status(404).send(errorPage404Message('The resource you’re looking for does not exist.'));
} else {
console.error(e);
res.status(500).send(errorPage50x);
res.status(500).send(errorPage50x('API Error'));
}
}
};
Expand Down Expand Up @@ -75,10 +75,8 @@ const getHardCodedSchemata = ({
.map((schema) => schema.trim())
.map((schemaName) => ({ schemaName }))
},
// @ts-ignore
schemaNames: { nodes: [] },
// @ts-ignore
apiModules: { nodes: [] }
schemaNames: { nodes: [] as Array<{ schemaName: string }> },
apiModules: { nodes: [] as Array<any> }
}
}
};
Expand Down Expand Up @@ -107,10 +105,8 @@ const getMetaSchema = ({
schemaNamesFromExt: {
nodes: schemata.map((schemaName: string) => ({ schemaName }))
},
// @ts-ignore
schemaNames: { nodes: [] },
// @ts-ignore
apiModules: { nodes: [] }
schemaNames: { nodes: [] as Array<{ schemaName: string }> },
apiModules: { nodes: [] as Array<any> }
}
}
};
Expand Down
1 change: 0 additions & 1 deletion packages/server/src/middleware/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ export const createAuthenticateMiddleware = (opts: LaunchQLOptions): RequestHand
}
}

// @ts-ignore - augment req with `token`
req.token = token;
}

Expand Down
48 changes: 48 additions & 0 deletions packages/server/src/middleware/directSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { LaunchQLOptions } from '@launchql/types';
import { Response, NextFunction } from 'express';
import errorPage404 from '../errors/404-message';
import errorPage50x from '../errors/50x';

export const createDirectSchemaMiddleware = (opts: LaunchQLOptions) => {
return async (req: any, res: Response, next: NextFunction): Promise<void> => {
try {
const schemas = opts.graphile?.schema || [];
const schemaArray = Array.isArray(schemas) ? schemas : [schemas];

if (!schemaArray.length) {
res.status(404).send(errorPage404('no schema for hard-coded schema'));
return;
}

// let's just pass this in from the CLI when it's needed
const svc = {
data: {
api: {
databaseId: 'direct-schema',
isPublic: opts.graphile.isPublic,
dbname: opts.pg.database,

// TODO: allow these to be passed in when useMetaApi is off...
anonRole: opts.graphile.anonRole,
roleName: opts.graphile.roleName,
schemaNamesFromExt: {
nodes: schemaArray.map(schemaName => ({ schemaName }))
},
schemaNames: { nodes: [] as Array<{ schemaName: string }> },
apiModules: { nodes: [] as Array<any> }
}
}
};

req.apiInfo = svc;
req.databaseId = svc.data.api.databaseId;
req.svc_key = 'direct-schema';

next();
} catch (e: any) {
console.error(e);
res.status(500).send(errorPage50x('directSchema'));
return;
}
};
};
23 changes: 18 additions & 5 deletions packages/server/src/middleware/graphile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,22 @@ export const graphile = (lOpts: LaunchQLOptions): RequestHandler => {
const key = req.svc_key;
const { dbname } = api;
const { anonRole, roleName } = api;

const { schemaNamesFromExt, schemaNames } = api;
const schemas = []
.concat(schemaNamesFromExt.nodes.map(({ schemaName }: any) => schemaName))
.concat(schemaNames.nodes.map(({ schemaName }: any) => schemaName));

let schemas = [] as Array<{ schemaName: string }>

if (api.schemaNamesFromExt && api.schemaNamesFromExt.nodes) {
schemas = schemas.concat(api.schemaNamesFromExt.nodes.map(({ schemaName }: any) => schemaName));
}

if (api.schemaNames && api.schemaNames.nodes) {
schemas = schemas.concat(api.schemaNames.nodes.map(({ schemaName }: any) => schemaName));
}

if (schemas.length === 0 && lOpts.graphile?.schema) {
const directSchemas = lOpts.graphile.schema;
// @ts-ignore
schemas = Array.isArray(directSchemas) ? directSchemas : [directSchemas];
}

if (graphileCache.has(key)) {
const { handler } = graphileCache.get(key)!
Expand All @@ -29,6 +40,7 @@ export const graphile = (lOpts: LaunchQLOptions): RequestHandler => {
...lOpts,
graphile: {
...lOpts.graphile,
// @ts-ignore
schema: schemas
}
});
Expand Down Expand Up @@ -87,6 +99,7 @@ export const graphile = (lOpts: LaunchQLOptions): RequestHandler => {
...lOpts.pg,
database: dbname
});
// @ts-ignore
const handler = postgraphile(pgPool, schemas, opts);

graphileCache.set(key, {
Expand Down
54 changes: 43 additions & 11 deletions packages/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { createAuthenticateMiddleware } from './middleware/auth';
import { graphile } from './middleware/graphile';
import { cors } from './middleware/cors';
import { createApiMiddleware } from './middleware/api';
import { createDirectSchemaMiddleware } from './middleware/directSchema';
import { flush, flushService } from './middleware/flush';
import requestIp from 'request-ip';
import { Pool, PoolClient } from 'pg';
Expand All @@ -33,25 +34,56 @@ class Server {

constructor(opts: LaunchQLOptions) {
this.opts = opts;

const app = express();
const api = createApiMiddleware(opts);
const authenticate = createAuthenticateMiddleware(opts);


healthz(app);
trustProxy(app, opts.server.trustProxy);
trustProxy(app, opts.server?.trustProxy);
app.use(poweredBy('launchql'));
app.use(graphqlUploadExpress());
app.use(parseDomains() as RequestHandler);
app.use(requestIp.mw());
app.use(api);
app.use(cors as any);
app.use(authenticate);
app.use(graphile(opts));
app.use(flush);


this.registerMiddleware(app);

this.app = app;
}

private registerMiddleware(app: Express): void {
const middlewareOpts = this.opts.server?.middleware || {};

if (middlewareOpts.useMetaApi !== false) {
const api = createApiMiddleware(this.opts);
app.use(api);
} else {
const directSchema = createDirectSchemaMiddleware(this.opts);
app.use(directSchema);
}

if (middlewareOpts.useCors !== false) {
app.use(cors as any);
}

if (middlewareOpts.useAuth !== false) {
const authenticate = createAuthenticateMiddleware(this.opts);
app.use(authenticate);
}

if (middlewareOpts.useGraphile !== false) {
const graphileMiddleware = graphile(this.opts);
app.use(graphileMiddleware);
}

if (middlewareOpts.useFlush !== false) {
app.use(flush);
}

if (Array.isArray(middlewareOpts.customMiddleware)) {
middlewareOpts.customMiddleware.forEach((middleware, index) => {
app.use(middleware);
});
}
}

listen(): void {
const { server } = this.opts;
Expand Down
Loading