@constructive-io/graphql-server 2.19.1 → 2.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/codegen/orm/index.d.ts +7 -603
- package/codegen/orm/index.js +4 -127
- package/codegen/orm/input-types.d.ts +14682 -7790
- package/codegen/orm/input-types.js +0 -5
- package/codegen/orm/models/api.d.ts +6 -6
- package/codegen/orm/models/api.js +3 -4
- package/codegen/orm/models/domain.d.ts +6 -6
- package/codegen/orm/models/domain.js +3 -4
- package/codegen/orm/models/index.d.ts +1 -58
- package/codegen/orm/models/index.js +3 -118
- package/codegen/orm/mutation/index.d.ts +1 -525
- package/codegen/orm/mutation/index.js +2 -591
- package/codegen/orm/query/index.d.ts +3 -257
- package/codegen/orm/query/index.js +8 -274
- package/codegen/orm/query-builder.d.ts +2 -1
- package/codegen/orm/query-builder.js +376 -129
- package/codegen/orm/select-types.d.ts +33 -0
- package/esm/codegen/orm/index.js +4 -127
- package/esm/codegen/orm/input-types.js +0 -5
- package/esm/codegen/orm/models/api.js +3 -4
- package/esm/codegen/orm/models/domain.js +3 -4
- package/esm/codegen/orm/models/index.js +1 -58
- package/esm/codegen/orm/mutation/index.js +2 -591
- package/esm/codegen/orm/query/index.js +8 -274
- package/esm/codegen/orm/query-builder.js +343 -129
- package/esm/middleware/api.js +145 -173
- package/esm/middleware/gql.js +72 -71
- package/middleware/api.d.ts +16 -3
- package/middleware/api.js +148 -173
- package/middleware/gql.d.ts +174 -24
- package/middleware/gql.js +76 -76
- package/middleware/types.d.ts +3 -19
- package/package.json +7 -4
- package/types.d.ts +17 -44
- package/codegen/orm/models/apiExtension.d.ts +0 -42
- package/codegen/orm/models/apiExtension.js +0 -77
- package/codegen/orm/models/apiModule.d.ts +0 -42
- package/codegen/orm/models/apiModule.js +0 -77
- package/codegen/orm/models/apiSchema.d.ts +0 -42
- package/codegen/orm/models/apiSchema.js +0 -77
- package/codegen/orm/models/app.d.ts +0 -42
- package/codegen/orm/models/app.js +0 -77
- package/codegen/orm/models/checkConstraint.d.ts +0 -42
- package/codegen/orm/models/checkConstraint.js +0 -77
- package/codegen/orm/models/connectedAccountsModule.d.ts +0 -42
- package/codegen/orm/models/connectedAccountsModule.js +0 -77
- package/codegen/orm/models/cryptoAddressesModule.d.ts +0 -42
- package/codegen/orm/models/cryptoAddressesModule.js +0 -77
- package/codegen/orm/models/cryptoAuthModule.d.ts +0 -42
- package/codegen/orm/models/cryptoAuthModule.js +0 -77
- package/codegen/orm/models/database.d.ts +0 -42
- package/codegen/orm/models/database.js +0 -77
- package/codegen/orm/models/databaseExtension.d.ts +0 -42
- package/codegen/orm/models/databaseExtension.js +0 -77
- package/codegen/orm/models/databaseProvision.d.ts +0 -42
- package/codegen/orm/models/databaseProvision.js +0 -77
- package/codegen/orm/models/defaultIdsModule.d.ts +0 -42
- package/codegen/orm/models/defaultIdsModule.js +0 -77
- package/codegen/orm/models/denormalizedTableField.d.ts +0 -42
- package/codegen/orm/models/denormalizedTableField.js +0 -77
- package/codegen/orm/models/emailsModule.d.ts +0 -42
- package/codegen/orm/models/emailsModule.js +0 -77
- package/codegen/orm/models/encryptedSecretsModule.d.ts +0 -42
- package/codegen/orm/models/encryptedSecretsModule.js +0 -77
- package/codegen/orm/models/extension.d.ts +0 -42
- package/codegen/orm/models/extension.js +0 -77
- package/codegen/orm/models/field.d.ts +0 -42
- package/codegen/orm/models/field.js +0 -77
- package/codegen/orm/models/fieldModule.d.ts +0 -42
- package/codegen/orm/models/fieldModule.js +0 -77
- package/codegen/orm/models/foreignKeyConstraint.d.ts +0 -42
- package/codegen/orm/models/foreignKeyConstraint.js +0 -77
- package/codegen/orm/models/fullTextSearch.d.ts +0 -42
- package/codegen/orm/models/fullTextSearch.js +0 -77
- package/codegen/orm/models/hierarchyModule.d.ts +0 -42
- package/codegen/orm/models/hierarchyModule.js +0 -77
- package/codegen/orm/models/indexModel.d.ts +0 -42
- package/codegen/orm/models/indexModel.js +0 -77
- package/codegen/orm/models/invitesModule.d.ts +0 -42
- package/codegen/orm/models/invitesModule.js +0 -77
- package/codegen/orm/models/levelsModule.d.ts +0 -42
- package/codegen/orm/models/levelsModule.js +0 -77
- package/codegen/orm/models/limitFunction.d.ts +0 -42
- package/codegen/orm/models/limitFunction.js +0 -77
- package/codegen/orm/models/limitsModule.d.ts +0 -42
- package/codegen/orm/models/limitsModule.js +0 -77
- package/codegen/orm/models/membershipTypesModule.d.ts +0 -42
- package/codegen/orm/models/membershipTypesModule.js +0 -77
- package/codegen/orm/models/membershipsModule.d.ts +0 -42
- package/codegen/orm/models/membershipsModule.js +0 -77
- package/codegen/orm/models/module.d.ts +0 -42
- package/codegen/orm/models/module.js +0 -77
- package/codegen/orm/models/moduleDefinition.d.ts +0 -42
- package/codegen/orm/models/moduleDefinition.js +0 -77
- package/codegen/orm/models/moduleField.d.ts +0 -42
- package/codegen/orm/models/moduleField.js +0 -77
- package/codegen/orm/models/moduleInputRecord.d.ts +0 -42
- package/codegen/orm/models/moduleInputRecord.js +0 -77
- package/codegen/orm/models/moduleOutput.d.ts +0 -42
- package/codegen/orm/models/moduleOutput.js +0 -77
- package/codegen/orm/models/permissionsModule.d.ts +0 -42
- package/codegen/orm/models/permissionsModule.js +0 -77
- package/codegen/orm/models/phoneNumbersModule.d.ts +0 -42
- package/codegen/orm/models/phoneNumbersModule.js +0 -77
- package/codegen/orm/models/policy.d.ts +0 -42
- package/codegen/orm/models/policy.js +0 -77
- package/codegen/orm/models/primaryKeyConstraint.d.ts +0 -42
- package/codegen/orm/models/primaryKeyConstraint.js +0 -77
- package/codegen/orm/models/procedure.d.ts +0 -42
- package/codegen/orm/models/procedure.js +0 -77
- package/codegen/orm/models/profilesModule.d.ts +0 -42
- package/codegen/orm/models/profilesModule.js +0 -77
- package/codegen/orm/models/rlsFunction.d.ts +0 -42
- package/codegen/orm/models/rlsFunction.js +0 -77
- package/codegen/orm/models/rlsModule.d.ts +0 -42
- package/codegen/orm/models/rlsModule.js +0 -77
- package/codegen/orm/models/schema.d.ts +0 -42
- package/codegen/orm/models/schema.js +0 -77
- package/codegen/orm/models/schemaGrant.d.ts +0 -42
- package/codegen/orm/models/schemaGrant.js +0 -77
- package/codegen/orm/models/secretsModule.d.ts +0 -42
- package/codegen/orm/models/secretsModule.js +0 -77
- package/codegen/orm/models/site.d.ts +0 -42
- package/codegen/orm/models/site.js +0 -77
- package/codegen/orm/models/siteMetadatum.d.ts +0 -42
- package/codegen/orm/models/siteMetadatum.js +0 -77
- package/codegen/orm/models/siteModule.d.ts +0 -42
- package/codegen/orm/models/siteModule.js +0 -77
- package/codegen/orm/models/siteTheme.d.ts +0 -42
- package/codegen/orm/models/siteTheme.js +0 -77
- package/codegen/orm/models/table.d.ts +0 -42
- package/codegen/orm/models/table.js +0 -77
- package/codegen/orm/models/tableGrant.d.ts +0 -42
- package/codegen/orm/models/tableGrant.js +0 -77
- package/codegen/orm/models/tokensModule.d.ts +0 -42
- package/codegen/orm/models/tokensModule.js +0 -77
- package/codegen/orm/models/trigger.d.ts +0 -42
- package/codegen/orm/models/trigger.js +0 -77
- package/codegen/orm/models/triggerFunction.d.ts +0 -42
- package/codegen/orm/models/triggerFunction.js +0 -77
- package/codegen/orm/models/uniqueConstraint.d.ts +0 -42
- package/codegen/orm/models/uniqueConstraint.js +0 -77
- package/codegen/orm/models/userAuthModule.d.ts +0 -42
- package/codegen/orm/models/userAuthModule.js +0 -77
- package/codegen/orm/models/usersModule.d.ts +0 -42
- package/codegen/orm/models/usersModule.js +0 -77
- package/codegen/orm/models/uuidModule.d.ts +0 -42
- package/codegen/orm/models/uuidModule.js +0 -77
- package/esm/codegen/orm/models/apiExtension.js +0 -73
- package/esm/codegen/orm/models/apiModule.js +0 -73
- package/esm/codegen/orm/models/apiSchema.js +0 -73
- package/esm/codegen/orm/models/app.js +0 -73
- package/esm/codegen/orm/models/checkConstraint.js +0 -73
- package/esm/codegen/orm/models/connectedAccountsModule.js +0 -73
- package/esm/codegen/orm/models/cryptoAddressesModule.js +0 -73
- package/esm/codegen/orm/models/cryptoAuthModule.js +0 -73
- package/esm/codegen/orm/models/database.js +0 -73
- package/esm/codegen/orm/models/databaseExtension.js +0 -73
- package/esm/codegen/orm/models/databaseProvision.js +0 -73
- package/esm/codegen/orm/models/defaultIdsModule.js +0 -73
- package/esm/codegen/orm/models/denormalizedTableField.js +0 -73
- package/esm/codegen/orm/models/emailsModule.js +0 -73
- package/esm/codegen/orm/models/encryptedSecretsModule.js +0 -73
- package/esm/codegen/orm/models/extension.js +0 -73
- package/esm/codegen/orm/models/field.js +0 -73
- package/esm/codegen/orm/models/fieldModule.js +0 -73
- package/esm/codegen/orm/models/foreignKeyConstraint.js +0 -73
- package/esm/codegen/orm/models/fullTextSearch.js +0 -73
- package/esm/codegen/orm/models/hierarchyModule.js +0 -73
- package/esm/codegen/orm/models/indexModel.js +0 -73
- package/esm/codegen/orm/models/invitesModule.js +0 -73
- package/esm/codegen/orm/models/levelsModule.js +0 -73
- package/esm/codegen/orm/models/limitFunction.js +0 -73
- package/esm/codegen/orm/models/limitsModule.js +0 -73
- package/esm/codegen/orm/models/membershipTypesModule.js +0 -73
- package/esm/codegen/orm/models/membershipsModule.js +0 -73
- package/esm/codegen/orm/models/module.js +0 -73
- package/esm/codegen/orm/models/moduleDefinition.js +0 -73
- package/esm/codegen/orm/models/moduleField.js +0 -73
- package/esm/codegen/orm/models/moduleInputRecord.js +0 -73
- package/esm/codegen/orm/models/moduleOutput.js +0 -73
- package/esm/codegen/orm/models/permissionsModule.js +0 -73
- package/esm/codegen/orm/models/phoneNumbersModule.js +0 -73
- package/esm/codegen/orm/models/policy.js +0 -73
- package/esm/codegen/orm/models/primaryKeyConstraint.js +0 -73
- package/esm/codegen/orm/models/procedure.js +0 -73
- package/esm/codegen/orm/models/profilesModule.js +0 -73
- package/esm/codegen/orm/models/rlsFunction.js +0 -73
- package/esm/codegen/orm/models/rlsModule.js +0 -73
- package/esm/codegen/orm/models/schema.js +0 -73
- package/esm/codegen/orm/models/schemaGrant.js +0 -73
- package/esm/codegen/orm/models/secretsModule.js +0 -73
- package/esm/codegen/orm/models/site.js +0 -73
- package/esm/codegen/orm/models/siteMetadatum.js +0 -73
- package/esm/codegen/orm/models/siteModule.js +0 -73
- package/esm/codegen/orm/models/siteTheme.js +0 -73
- package/esm/codegen/orm/models/table.js +0 -73
- package/esm/codegen/orm/models/tableGrant.js +0 -73
- package/esm/codegen/orm/models/tokensModule.js +0 -73
- package/esm/codegen/orm/models/trigger.js +0 -73
- package/esm/codegen/orm/models/triggerFunction.js +0 -73
- package/esm/codegen/orm/models/uniqueConstraint.js +0 -73
- package/esm/codegen/orm/models/userAuthModule.js +0 -73
- package/esm/codegen/orm/models/usersModule.js +0 -73
- package/esm/codegen/orm/models/uuidModule.js +0 -73
package/esm/middleware/api.js
CHANGED
|
@@ -1,50 +1,21 @@
|
|
|
1
1
|
import { getNodeEnv } from '@constructive-io/graphql-env';
|
|
2
2
|
import { Logger } from '@pgpmjs/logger';
|
|
3
3
|
import { svcCache } from '@pgpmjs/server-utils';
|
|
4
|
+
import { parseUrl } from '@constructive-io/url-domains';
|
|
4
5
|
import { getSchema, GraphileQuery } from 'graphile-query';
|
|
5
6
|
import { getGraphileSettings } from 'graphile-settings';
|
|
6
7
|
import { getPgPool } from 'pg-cache';
|
|
7
8
|
import errorPage50x from '../errors/50x';
|
|
8
9
|
import errorPage404Message from '../errors/404-message';
|
|
9
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Normalizes API records into ApiStructure
|
|
12
|
+
*/
|
|
13
|
+
import { apiListSelect, apiSelect, connectionFirst, createGraphileOrm, domainSelect, normalizeApiRecord, } from './gql';
|
|
10
14
|
import './types'; // for Request type
|
|
15
|
+
export { normalizeApiRecord } from './gql';
|
|
11
16
|
const log = new Logger('api');
|
|
12
17
|
const isDev = () => getNodeEnv() === 'development';
|
|
13
|
-
const
|
|
14
|
-
const api = svc.data.api;
|
|
15
|
-
const schemaNames = api.apiExtensions?.nodes?.map((n) => n.schemaName) || [];
|
|
16
|
-
const additionalSchemas = api.schemasByApiSchemaApiIdAndSchemaId?.nodes?.map((n) => n.schemaName) || [];
|
|
17
|
-
let domains = [];
|
|
18
|
-
if (api.database?.sites?.nodes) {
|
|
19
|
-
domains = api.database.sites.nodes.reduce((acc, site) => {
|
|
20
|
-
if (site.domains?.nodes && site.domains.nodes.length) {
|
|
21
|
-
const siteUrls = site.domains.nodes.map((domain) => {
|
|
22
|
-
const hostname = domain.subdomain
|
|
23
|
-
? `${domain.subdomain}.${domain.domain}`
|
|
24
|
-
: domain.domain;
|
|
25
|
-
const protocol = domain.domain === 'localhost' ? 'http://' : 'https://';
|
|
26
|
-
return protocol + hostname;
|
|
27
|
-
});
|
|
28
|
-
return [...acc, ...siteUrls];
|
|
29
|
-
}
|
|
30
|
-
return acc;
|
|
31
|
-
}, []);
|
|
32
|
-
}
|
|
33
|
-
return {
|
|
34
|
-
dbname: api.dbname,
|
|
35
|
-
anonRole: api.anonRole,
|
|
36
|
-
roleName: api.roleName,
|
|
37
|
-
schema: [...schemaNames, ...additionalSchemas],
|
|
38
|
-
apiModules: api.apiModules?.nodes?.map((node) => ({
|
|
39
|
-
name: node.name,
|
|
40
|
-
data: node.data,
|
|
41
|
-
})) || [],
|
|
42
|
-
rlsModule: api.rlsModule,
|
|
43
|
-
domains,
|
|
44
|
-
databaseId: api.databaseId,
|
|
45
|
-
isPublic: api.isPublic,
|
|
46
|
-
};
|
|
47
|
-
};
|
|
18
|
+
const isApiError = (svc) => !!svc && typeof svc.errorHtml === 'string';
|
|
48
19
|
const getPortFromRequest = (req) => {
|
|
49
20
|
const host = req.headers.host;
|
|
50
21
|
if (!host)
|
|
@@ -52,6 +23,18 @@ const getPortFromRequest = (req) => {
|
|
|
52
23
|
const parts = host.split(':');
|
|
53
24
|
return parts.length === 2 ? `:${parts[1]}` : null;
|
|
54
25
|
};
|
|
26
|
+
const parseCommaSeparatedHeader = (value) => value
|
|
27
|
+
.split(',')
|
|
28
|
+
.map((item) => item.trim())
|
|
29
|
+
.filter((item) => item.length > 0);
|
|
30
|
+
const getUrlDomains = (req) => {
|
|
31
|
+
const fullUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
|
|
32
|
+
const parsed = parseUrl(fullUrl);
|
|
33
|
+
return {
|
|
34
|
+
domain: parsed.domain ?? '',
|
|
35
|
+
subdomains: parsed.subdomains ?? [],
|
|
36
|
+
};
|
|
37
|
+
};
|
|
55
38
|
export const getSubdomain = (reqDomains) => {
|
|
56
39
|
const names = reqDomains.filter((name) => !['www'].includes(name));
|
|
57
40
|
return !names.length ? null : names.join('.');
|
|
@@ -59,9 +42,9 @@ export const getSubdomain = (reqDomains) => {
|
|
|
59
42
|
export const createApiMiddleware = (opts) => {
|
|
60
43
|
return async (req, res, next) => {
|
|
61
44
|
if (opts.api?.enableServicesApi === false) {
|
|
62
|
-
const schemas = opts.api.exposedSchemas;
|
|
63
|
-
const anonRole = opts.api.anonRole;
|
|
64
|
-
const roleName = opts.api.roleName;
|
|
45
|
+
const schemas = opts.api.exposedSchemas ?? [];
|
|
46
|
+
const anonRole = opts.api.anonRole ?? '';
|
|
47
|
+
const roleName = opts.api.roleName ?? '';
|
|
65
48
|
const databaseId = opts.api.defaultDatabaseId;
|
|
66
49
|
const api = {
|
|
67
50
|
dbname: opts.pg?.database ?? '',
|
|
@@ -79,133 +62,101 @@ export const createApiMiddleware = (opts) => {
|
|
|
79
62
|
return next();
|
|
80
63
|
}
|
|
81
64
|
try {
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
65
|
+
const apiConfig = await getApiConfig(opts, req);
|
|
66
|
+
if (isApiError(apiConfig)) {
|
|
84
67
|
res
|
|
85
68
|
.status(404)
|
|
86
|
-
.send(errorPage404Message('API not found',
|
|
69
|
+
.send(errorPage404Message('API not found', apiConfig.errorHtml));
|
|
87
70
|
return;
|
|
88
71
|
}
|
|
89
|
-
else if (!
|
|
72
|
+
else if (!apiConfig) {
|
|
90
73
|
res
|
|
91
74
|
.status(404)
|
|
92
75
|
.send(errorPage404Message('API service not found for the given domain/subdomain.'));
|
|
93
76
|
return;
|
|
94
77
|
}
|
|
95
|
-
|
|
96
|
-
req.
|
|
97
|
-
req.databaseId = api.databaseId;
|
|
78
|
+
req.api = apiConfig;
|
|
79
|
+
req.databaseId = apiConfig.databaseId;
|
|
98
80
|
if (isDev())
|
|
99
|
-
log.debug(`Resolved API: db=${
|
|
81
|
+
log.debug(`Resolved API: db=${apiConfig.dbname}, schemas=[${apiConfig.schema?.join(', ')}]`);
|
|
100
82
|
next();
|
|
101
83
|
}
|
|
102
|
-
catch (
|
|
103
|
-
|
|
104
|
-
|
|
84
|
+
catch (error) {
|
|
85
|
+
const err = error;
|
|
86
|
+
if (err.code === 'NO_VALID_SCHEMAS') {
|
|
87
|
+
res.status(404).send(errorPage404Message(err.message));
|
|
105
88
|
}
|
|
106
|
-
else if (
|
|
89
|
+
else if (err.message?.match(/does not exist/)) {
|
|
107
90
|
res
|
|
108
91
|
.status(404)
|
|
109
92
|
.send(errorPage404Message("The resource you're looking for does not exist."));
|
|
110
93
|
}
|
|
111
94
|
else {
|
|
112
|
-
log.error('API middleware error:',
|
|
95
|
+
log.error('API middleware error:', err);
|
|
113
96
|
res.status(500).send(errorPage50x);
|
|
114
97
|
}
|
|
115
98
|
}
|
|
116
99
|
};
|
|
117
100
|
};
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
nodes: schemata
|
|
129
|
-
.split(',')
|
|
130
|
-
.map((schema) => schema.trim())
|
|
131
|
-
.map((schemaName) => ({ schemaName })),
|
|
132
|
-
},
|
|
133
|
-
schemaNames: { nodes: [] },
|
|
134
|
-
apiModules: [],
|
|
135
|
-
},
|
|
136
|
-
},
|
|
101
|
+
const createAdminApiStructure = ({ opts, schemata, key, databaseId, }) => {
|
|
102
|
+
const api = {
|
|
103
|
+
dbname: opts.pg?.database ?? '',
|
|
104
|
+
anonRole: 'administrator',
|
|
105
|
+
roleName: 'administrator',
|
|
106
|
+
schema: schemata,
|
|
107
|
+
apiModules: [],
|
|
108
|
+
domains: [],
|
|
109
|
+
databaseId,
|
|
110
|
+
isPublic: false,
|
|
137
111
|
};
|
|
138
|
-
svcCache.set(key,
|
|
139
|
-
return
|
|
112
|
+
svcCache.set(key, api);
|
|
113
|
+
return api;
|
|
140
114
|
};
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
databaseId,
|
|
148
|
-
isPublic: false,
|
|
149
|
-
dbname: opts.pg.database,
|
|
150
|
-
anonRole: 'administrator',
|
|
151
|
-
roleName: 'administrator',
|
|
152
|
-
schemaNamesFromExt: {
|
|
153
|
-
nodes: schemata.map((schemaName) => ({ schemaName })),
|
|
154
|
-
},
|
|
155
|
-
schemaNames: { nodes: [] },
|
|
156
|
-
apiModules: [],
|
|
157
|
-
},
|
|
158
|
-
},
|
|
115
|
+
const queryServiceByDomainAndSubdomain = async ({ opts, key, domainModel, domain, subdomain, }) => {
|
|
116
|
+
const where = {
|
|
117
|
+
domain: { equalTo: domain },
|
|
118
|
+
subdomain: subdomain === null || subdomain === undefined
|
|
119
|
+
? { isNull: true }
|
|
120
|
+
: { equalTo: subdomain },
|
|
159
121
|
};
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const doc = buildDomainLookup({ domain, subdomain });
|
|
165
|
-
const result = await client.query({
|
|
166
|
-
role: 'administrator',
|
|
167
|
-
query: doc.document,
|
|
168
|
-
variables: doc.variables,
|
|
169
|
-
});
|
|
170
|
-
if (result.errors?.length) {
|
|
122
|
+
const result = await domainModel
|
|
123
|
+
.findFirst({ select: domainSelect, where })
|
|
124
|
+
.execute();
|
|
125
|
+
if (!result.ok) {
|
|
171
126
|
log.error('GraphQL query errors:', result.errors);
|
|
172
127
|
return null;
|
|
173
128
|
}
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return svc;
|
|
183
|
-
}
|
|
184
|
-
return null;
|
|
129
|
+
const domainRecord = result.data.domains.nodes[0];
|
|
130
|
+
const api = domainRecord?.api;
|
|
131
|
+
const apiPublic = opts.api?.isPublic;
|
|
132
|
+
if (!api || api.isPublic !== apiPublic)
|
|
133
|
+
return null;
|
|
134
|
+
const apiStructure = normalizeApiRecord(api);
|
|
135
|
+
svcCache.set(key, apiStructure);
|
|
136
|
+
return apiStructure;
|
|
185
137
|
};
|
|
186
|
-
const queryServiceByApiName = async ({ opts, key,
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (result.errors?.length) {
|
|
138
|
+
export const queryServiceByApiName = async ({ opts, key, queryOps, databaseId, name, }) => {
|
|
139
|
+
if (!databaseId)
|
|
140
|
+
return null;
|
|
141
|
+
const result = await queryOps
|
|
142
|
+
.apiByDatabaseIdAndName({ databaseId, name }, { select: apiSelect })
|
|
143
|
+
.execute();
|
|
144
|
+
if (!result.ok) {
|
|
194
145
|
log.error('GraphQL query errors:', result.errors);
|
|
195
146
|
return null;
|
|
196
147
|
}
|
|
197
|
-
const
|
|
148
|
+
const api = result.data.apiByDatabaseIdAndName;
|
|
198
149
|
const apiPublic = opts.api?.isPublic;
|
|
199
|
-
if (
|
|
200
|
-
const
|
|
201
|
-
svcCache.set(key,
|
|
202
|
-
return
|
|
150
|
+
if (api && api.isPublic === apiPublic) {
|
|
151
|
+
const apiStructure = normalizeApiRecord(api);
|
|
152
|
+
svcCache.set(key, apiStructure);
|
|
153
|
+
return apiStructure;
|
|
203
154
|
}
|
|
204
155
|
return null;
|
|
205
156
|
};
|
|
206
|
-
const getSvcKey = (opts, req) => {
|
|
207
|
-
const domain = req
|
|
208
|
-
const key =
|
|
157
|
+
export const getSvcKey = (opts, req) => {
|
|
158
|
+
const { domain, subdomains } = getUrlDomains(req);
|
|
159
|
+
const key = subdomains
|
|
209
160
|
.filter((name) => !['www'].includes(name))
|
|
210
161
|
.concat(domain)
|
|
211
162
|
.join('.');
|
|
@@ -229,95 +180,118 @@ const validateSchemata = async (pool, schemata) => {
|
|
|
229
180
|
};
|
|
230
181
|
export const getApiConfig = async (opts, req) => {
|
|
231
182
|
const rootPgPool = getPgPool(opts.pg);
|
|
232
|
-
|
|
233
|
-
const subdomain = getSubdomain(
|
|
234
|
-
const domain = req.urlDomains.domain;
|
|
183
|
+
const { domain, subdomains } = getUrlDomains(req);
|
|
184
|
+
const subdomain = getSubdomain(subdomains);
|
|
235
185
|
const key = getSvcKey(opts, req);
|
|
236
186
|
req.svc_key = key;
|
|
237
|
-
let
|
|
187
|
+
let apiConfig;
|
|
238
188
|
if (svcCache.has(key)) {
|
|
239
189
|
if (isDev())
|
|
240
190
|
log.debug(`Cache HIT for key=${key}`);
|
|
241
|
-
|
|
191
|
+
apiConfig = svcCache.get(key);
|
|
242
192
|
}
|
|
243
193
|
else {
|
|
244
194
|
if (isDev())
|
|
245
195
|
log.debug(`Cache MISS for key=${key}, looking up API`);
|
|
246
196
|
const apiOpts = opts.api || {};
|
|
247
|
-
const
|
|
248
|
-
const
|
|
197
|
+
const apiPublic = apiOpts.isPublic;
|
|
198
|
+
const schemataHeader = req.get('X-Schemata');
|
|
199
|
+
const apiNameHeader = req.get('X-Api-Name');
|
|
200
|
+
const metaSchemaHeader = req.get('X-Meta-Schema');
|
|
201
|
+
const databaseIdHeader = req.get('X-Database-Id');
|
|
202
|
+
const headerSchemata = schemataHeader
|
|
203
|
+
? parseCommaSeparatedHeader(schemataHeader)
|
|
204
|
+
: [];
|
|
205
|
+
const candidateSchemata = apiPublic === false && headerSchemata.length
|
|
206
|
+
? Array.from(new Set([...(apiOpts.metaSchemas || []), ...headerSchemata]))
|
|
207
|
+
: apiOpts.metaSchemas || [];
|
|
208
|
+
const validatedSchemata = await validateSchemata(rootPgPool, candidateSchemata);
|
|
249
209
|
if (validatedSchemata.length === 0) {
|
|
250
|
-
const
|
|
251
|
-
|
|
210
|
+
const schemaSource = headerSchemata.length
|
|
211
|
+
? headerSchemata
|
|
212
|
+
: apiOpts.metaSchemas || [];
|
|
213
|
+
const label = headerSchemata.length ? 'X-Schemata' : 'metaSchemas';
|
|
214
|
+
const message = `No valid schemas found. Configured ${label}: [${schemaSource.join(', ')}]`;
|
|
252
215
|
if (isDev())
|
|
253
216
|
log.debug(message);
|
|
254
217
|
const error = new Error(message);
|
|
255
218
|
error.code = 'NO_VALID_SCHEMAS';
|
|
256
219
|
throw error;
|
|
257
220
|
}
|
|
221
|
+
const validSchemaSet = new Set(validatedSchemata);
|
|
222
|
+
const validatedHeaderSchemata = headerSchemata.filter((schemaName) => validSchemaSet.has(schemaName));
|
|
258
223
|
const settings = getGraphileSettings({
|
|
259
224
|
graphile: {
|
|
260
225
|
schema: validatedSchemata,
|
|
261
226
|
},
|
|
262
227
|
});
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const
|
|
228
|
+
const graphileSettings = {
|
|
229
|
+
...settings,
|
|
230
|
+
schema: validatedSchemata,
|
|
231
|
+
};
|
|
232
|
+
const schema = await getSchema(rootPgPool, graphileSettings);
|
|
233
|
+
const graphileClient = new GraphileQuery({
|
|
234
|
+
schema,
|
|
235
|
+
pool: rootPgPool,
|
|
236
|
+
settings: graphileSettings,
|
|
237
|
+
});
|
|
238
|
+
const orm = createGraphileOrm(graphileClient);
|
|
268
239
|
if (apiPublic === false) {
|
|
269
|
-
if (
|
|
270
|
-
|
|
240
|
+
if (schemataHeader) {
|
|
241
|
+
if (validatedHeaderSchemata.length === 0) {
|
|
242
|
+
return {
|
|
243
|
+
errorHtml: 'No valid schemas found for the supplied X-Schemata header.',
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
apiConfig = createAdminApiStructure({
|
|
271
247
|
opts,
|
|
248
|
+
schemata: validatedHeaderSchemata,
|
|
272
249
|
key,
|
|
273
|
-
|
|
274
|
-
databaseId: req.get('X-Database-Id'),
|
|
250
|
+
databaseId: databaseIdHeader,
|
|
275
251
|
});
|
|
276
252
|
}
|
|
277
|
-
else if (
|
|
278
|
-
|
|
253
|
+
else if (apiNameHeader) {
|
|
254
|
+
apiConfig = await queryServiceByApiName({
|
|
279
255
|
opts,
|
|
280
256
|
key,
|
|
281
|
-
|
|
282
|
-
name:
|
|
283
|
-
databaseId:
|
|
257
|
+
queryOps: orm.query,
|
|
258
|
+
name: apiNameHeader,
|
|
259
|
+
databaseId: databaseIdHeader,
|
|
284
260
|
});
|
|
285
261
|
}
|
|
286
|
-
else if (
|
|
287
|
-
|
|
262
|
+
else if (metaSchemaHeader) {
|
|
263
|
+
apiConfig = createAdminApiStructure({
|
|
288
264
|
opts,
|
|
265
|
+
schemata: validatedSchemata,
|
|
289
266
|
key,
|
|
290
|
-
databaseId:
|
|
267
|
+
databaseId: databaseIdHeader,
|
|
291
268
|
});
|
|
292
269
|
}
|
|
293
270
|
else {
|
|
294
|
-
|
|
271
|
+
apiConfig = await queryServiceByDomainAndSubdomain({
|
|
295
272
|
opts,
|
|
296
273
|
key,
|
|
297
|
-
|
|
274
|
+
domainModel: orm.domain,
|
|
298
275
|
domain,
|
|
299
276
|
subdomain,
|
|
300
277
|
});
|
|
301
278
|
}
|
|
302
279
|
}
|
|
303
280
|
else {
|
|
304
|
-
|
|
281
|
+
apiConfig = await queryServiceByDomainAndSubdomain({
|
|
305
282
|
opts,
|
|
306
283
|
key,
|
|
307
|
-
|
|
284
|
+
domainModel: orm.domain,
|
|
308
285
|
domain,
|
|
309
286
|
subdomain,
|
|
310
287
|
});
|
|
311
|
-
if (!
|
|
288
|
+
if (!apiConfig) {
|
|
289
|
+
// IMPORTANT NOTE: ONLY DO THIS IN DEV MODE
|
|
312
290
|
if (getNodeEnv() === 'development') {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
query: buildListApis().document,
|
|
318
|
-
});
|
|
319
|
-
if (!fallbackResult.errors?.length &&
|
|
320
|
-
fallbackResult.data?.apis?.nodes?.length) {
|
|
291
|
+
const fallbackResult = await orm.api
|
|
292
|
+
.findMany({ select: apiListSelect, first: connectionFirst })
|
|
293
|
+
.execute();
|
|
294
|
+
if (fallbackResult.ok && fallbackResult.data.apis.nodes.length) {
|
|
321
295
|
const port = getPortFromRequest(req);
|
|
322
296
|
const allDomains = fallbackResult.data.apis.nodes.flatMap((api) => api.domains.nodes.map((d) => ({
|
|
323
297
|
domain: d.domain,
|
|
@@ -329,7 +303,7 @@ export const getApiConfig = async (opts, req) => {
|
|
|
329
303
|
const linksHtml = allDomains.length
|
|
330
304
|
? `<ul class="mt-4 pl-5 list-disc space-y-1">` +
|
|
331
305
|
allDomains
|
|
332
|
-
.map((
|
|
306
|
+
.map((domainLink) => `<li><a href="${domainLink.href}" class="text-brand hover:underline">${domainLink.href}</a></li>`)
|
|
333
307
|
.join('') +
|
|
334
308
|
`</ul>`
|
|
335
309
|
: `<p class="text-gray-600">No APIs are currently registered for this database.</p>`;
|
|
@@ -339,13 +313,11 @@ export const getApiConfig = async (opts, req) => {
|
|
|
339
313
|
${linksHtml}
|
|
340
314
|
</div>
|
|
341
315
|
`.trim();
|
|
342
|
-
return {
|
|
343
|
-
errorHtml,
|
|
344
|
-
};
|
|
316
|
+
return { errorHtml };
|
|
345
317
|
}
|
|
346
318
|
}
|
|
347
319
|
}
|
|
348
320
|
}
|
|
349
321
|
}
|
|
350
|
-
return
|
|
322
|
+
return apiConfig;
|
|
351
323
|
};
|
package/esm/middleware/gql.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { OrmClient, } from '../codegen/orm/client';
|
|
2
|
+
import { ApiModel, DomainModel } from '../codegen/orm/models';
|
|
3
|
+
import { createQueryOperations } from '../codegen/orm/query';
|
|
4
|
+
export const connectionFirst = 1000;
|
|
5
|
+
export const apiSelect = {
|
|
3
6
|
databaseId: true,
|
|
4
7
|
dbname: true,
|
|
5
8
|
roleName: true,
|
|
@@ -10,15 +13,15 @@ const apiSelect = {
|
|
|
10
13
|
subdomain: true,
|
|
11
14
|
domain: true,
|
|
12
15
|
},
|
|
13
|
-
|
|
16
|
+
first: connectionFirst,
|
|
14
17
|
},
|
|
15
18
|
apiExtensions: {
|
|
16
19
|
select: { schemaName: true },
|
|
17
|
-
|
|
20
|
+
first: connectionFirst,
|
|
18
21
|
},
|
|
19
22
|
schemasByApiSchemaApiIdAndSchemaId: {
|
|
20
23
|
select: { schemaName: true },
|
|
21
|
-
|
|
24
|
+
first: connectionFirst,
|
|
22
25
|
},
|
|
23
26
|
rlsModule: {
|
|
24
27
|
select: {
|
|
@@ -29,30 +32,17 @@ const apiSelect = {
|
|
|
29
32
|
currentRoleId: true,
|
|
30
33
|
},
|
|
31
34
|
},
|
|
32
|
-
database: {
|
|
33
|
-
select: {
|
|
34
|
-
sites: {
|
|
35
|
-
select: {
|
|
36
|
-
domains: {
|
|
37
|
-
select: { subdomain: true, domain: true },
|
|
38
|
-
connection: true,
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
connection: true,
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
35
|
apiModules: {
|
|
46
36
|
select: { name: true, data: true },
|
|
47
|
-
|
|
37
|
+
first: connectionFirst,
|
|
48
38
|
},
|
|
49
39
|
};
|
|
50
|
-
const domainSelect = {
|
|
40
|
+
export const domainSelect = {
|
|
51
41
|
domain: true,
|
|
52
42
|
subdomain: true,
|
|
53
43
|
api: { select: apiSelect },
|
|
54
44
|
};
|
|
55
|
-
const
|
|
45
|
+
export const apiListSelect = {
|
|
56
46
|
id: true,
|
|
57
47
|
databaseId: true,
|
|
58
48
|
name: true,
|
|
@@ -62,59 +52,70 @@ const apisSelect = {
|
|
|
62
52
|
isPublic: true,
|
|
63
53
|
domains: {
|
|
64
54
|
select: { domain: true, subdomain: true },
|
|
65
|
-
|
|
66
|
-
},
|
|
67
|
-
database: {
|
|
68
|
-
select: {
|
|
69
|
-
sites: {
|
|
70
|
-
select: {
|
|
71
|
-
domains: {
|
|
72
|
-
select: { domain: true, subdomain: true },
|
|
73
|
-
connection: true,
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
connection: true,
|
|
77
|
-
},
|
|
78
|
-
},
|
|
55
|
+
first: connectionFirst,
|
|
79
56
|
},
|
|
80
57
|
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
export const buildDomainLookup = (vars) => {
|
|
87
|
-
const where = {
|
|
88
|
-
domain: { equalTo: vars.domain },
|
|
89
|
-
};
|
|
90
|
-
if (vars.subdomain === null || vars.subdomain === undefined) {
|
|
91
|
-
where.subdomain = { isNull: true };
|
|
58
|
+
class GraphileOrmClient extends OrmClient {
|
|
59
|
+
graphile;
|
|
60
|
+
constructor(graphile) {
|
|
61
|
+
super({ endpoint: 'http://localhost/graphql' });
|
|
62
|
+
this.graphile = graphile;
|
|
92
63
|
}
|
|
93
|
-
|
|
94
|
-
|
|
64
|
+
async execute(document, variables) {
|
|
65
|
+
const result = await this.graphile.query({
|
|
66
|
+
role: 'administrator',
|
|
67
|
+
query: document,
|
|
68
|
+
variables,
|
|
69
|
+
});
|
|
70
|
+
if (result.errors?.length) {
|
|
71
|
+
return {
|
|
72
|
+
ok: false,
|
|
73
|
+
data: null,
|
|
74
|
+
errors: result.errors,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
ok: true,
|
|
79
|
+
data: result.data,
|
|
80
|
+
errors: undefined,
|
|
81
|
+
};
|
|
95
82
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const { buildCustomDocument } = require('../codegen/orm/query-builder');
|
|
105
|
-
return buildCustomDocument('query', 'ApiByDatabaseIdAndName', 'apiByDatabaseIdAndName', apiSelect, vars, [
|
|
106
|
-
{ name: 'databaseId', type: 'UUID!' },
|
|
107
|
-
{ name: 'name', type: 'String!' },
|
|
108
|
-
]);
|
|
83
|
+
}
|
|
84
|
+
export const createGraphileOrm = (graphile) => {
|
|
85
|
+
const client = new GraphileOrmClient(graphile);
|
|
86
|
+
return {
|
|
87
|
+
api: new ApiModel(client),
|
|
88
|
+
domain: new DomainModel(client),
|
|
89
|
+
query: createQueryOperations(client),
|
|
90
|
+
};
|
|
109
91
|
};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
92
|
+
export const normalizeApiRecord = (api) => {
|
|
93
|
+
const schemaNames = (api.apiExtensions?.nodes ?? []).flatMap((node) => node.schemaName ? [node.schemaName] : []);
|
|
94
|
+
const additionalSchemas = (api.schemasByApiSchemaApiIdAndSchemaId?.nodes ?? []).flatMap((node) => (node.schemaName ? [node.schemaName] : []));
|
|
95
|
+
let domains = [];
|
|
96
|
+
if (api.domains?.nodes?.length) {
|
|
97
|
+
domains = api.domains.nodes.reduce((acc, domain) => {
|
|
98
|
+
if (!domain.domain)
|
|
99
|
+
return acc;
|
|
100
|
+
const hostname = domain.subdomain
|
|
101
|
+
? `${domain.subdomain}.${domain.domain}`
|
|
102
|
+
: domain.domain;
|
|
103
|
+
const protocol = domain.domain === 'localhost' ? 'http://' : 'https://';
|
|
104
|
+
return [...acc, protocol + hostname];
|
|
105
|
+
}, []);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
dbname: api.dbname,
|
|
109
|
+
anonRole: api.anonRole,
|
|
110
|
+
roleName: api.roleName,
|
|
111
|
+
schema: [...schemaNames, ...additionalSchemas],
|
|
112
|
+
apiModules: api.apiModules?.nodes?.map((node) => ({
|
|
113
|
+
name: node.name,
|
|
114
|
+
data: node.data,
|
|
115
|
+
})) || [],
|
|
116
|
+
rlsModule: api.rlsModule,
|
|
117
|
+
domains,
|
|
118
|
+
databaseId: api.databaseId,
|
|
119
|
+
isPublic: api.isPublic,
|
|
120
|
+
};
|
|
120
121
|
};
|
package/middleware/api.d.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
|
-
import { PgpmOptions } from '@pgpmjs/types';
|
|
2
1
|
import { NextFunction, Request, Response } from 'express';
|
|
2
|
+
/**
|
|
3
|
+
* Normalizes API records into ApiStructure
|
|
4
|
+
*/
|
|
5
|
+
import { type ApiQueryOps } from './gql';
|
|
6
|
+
import { ApiConfigResult, ApiOptions, ApiStructure } from '../types';
|
|
3
7
|
import './types';
|
|
8
|
+
export { normalizeApiRecord } from './gql';
|
|
4
9
|
export declare const getSubdomain: (reqDomains: string[]) => string | null;
|
|
5
|
-
export declare const createApiMiddleware: (opts:
|
|
6
|
-
export declare const
|
|
10
|
+
export declare const createApiMiddleware: (opts: ApiOptions) => (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
11
|
+
export declare const queryServiceByApiName: ({ opts, key, queryOps, databaseId, name, }: {
|
|
12
|
+
opts: ApiOptions;
|
|
13
|
+
key: string;
|
|
14
|
+
queryOps: ApiQueryOps;
|
|
15
|
+
databaseId?: string;
|
|
16
|
+
name: string;
|
|
17
|
+
}) => Promise<ApiStructure | null>;
|
|
18
|
+
export declare const getSvcKey: (opts: ApiOptions, req: Request) => string;
|
|
19
|
+
export declare const getApiConfig: (opts: ApiOptions, req: Request) => Promise<ApiConfigResult>;
|