diff --git a/packages/server/src/Api.ts b/packages/server/src/Api.ts new file mode 100644 index 00000000..db26f2d6 --- /dev/null +++ b/packages/server/src/Api.ts @@ -0,0 +1,38 @@ +import Handlers from './api/handlers'; +import { Routes } from './api/types'; +import { appRoutes } from './api/utils/routes/defaultRoutes'; +import serverAuth from './auth'; +import Tenants from './tenants'; +import Users from './users'; +import { Config } from './utils/Config'; + +export class Api { + config: Config; + users: Users; + tenants: Tenants; + routes: Routes; + handlers: { + GET: (req: Request) => Promise; + POST: (req: Request) => Promise; + DELETE: (req: Request) => Promise; + PUT: (req: Request) => Promise; + }; + constructor(config: Config) { + this.config = config; + this.users = new Users(config); + this.tenants = new Tenants(config); + this.routes = { + ...appRoutes(config?.routePrefix), + ...config?.routes, + }; + this.handlers = Handlers(this.routes, config); + } + + set headers(headers: Headers) { + this.users = new Users(this.config, headers); + this.tenants = new Tenants(this.config, headers); + } + async login(payload: { email: string; password: string }) { + this.headers = await serverAuth(this.config, this.handlers)(payload); + } +} diff --git a/packages/server/src/Server.ts b/packages/server/src/Server.ts index 04b4a8df..33ef3408 100644 --- a/packages/server/src/Server.ts +++ b/packages/server/src/Server.ts @@ -2,46 +2,10 @@ import { Pool } from 'pg'; import { ServerConfig } from './types'; import { Config } from './utils/Config'; -import Users from './users'; -import Tenants from './tenants'; import { watchTenantId, watchToken, watchUserId } from './utils/Event'; import DbManager from './db'; import { getServerId, makeServerId } from './utils/Server'; -import serverAuth from './auth'; -import { appRoutes } from './api/utils/routes/defaultRoutes'; -import Handlers from './api/handlers'; -import { Routes } from './api/types'; - -class Api { - config: Config; - users: Users; - tenants: Tenants; - routes: Routes; - handlers: { - GET: (req: Request) => Promise; - POST: (req: Request) => Promise; - DELETE: (req: Request) => Promise; - PUT: (req: Request) => Promise; - }; - constructor(config: Config) { - this.config = config; - this.users = new Users(config); - this.tenants = new Tenants(config); - this.routes = { - ...appRoutes(config?.routePrefix), - ...config?.routes, - }; - this.handlers = Handlers(this.routes, config); - } - - set headers(headers: Headers) { - this.users = new Users(this.config, headers); - this.tenants = new Tenants(this.config, headers); - } - async login(payload: { email: string; password: string }) { - this.headers = await serverAuth(this.config, this.handlers)(payload); - } -} +import { Api } from './Api'; export class Server { config: Config; @@ -136,7 +100,6 @@ export class Server { } } get db(): Pool { - // only need to interact with the knex object return this.manager.getConnection(this.config); } @@ -167,8 +130,11 @@ export class Server { return existing; } - this.servers.set(serverId, new Server(_config)); - return this.servers.get(serverId) as unknown as Server; + const newServer = new Server(_config); + + this.servers.set(serverId, newServer); + + return newServer; } } diff --git a/packages/server/src/db/DBManager.ts b/packages/server/src/db/DBManager.ts index 63244d71..6e574490 100644 --- a/packages/server/src/db/DBManager.ts +++ b/packages/server/src/db/DBManager.ts @@ -6,6 +6,7 @@ import Logger from '../utils/Logger'; import { ServerConfig } from '../types'; import NileDatabase from './NileInstance'; +import { isUUID } from './isUUID'; export default class DBManager { connections: Map; @@ -15,10 +16,10 @@ export default class DBManager { tenantId?: string | undefined | null, userId?: string | undefined | null ) { - if (tenantId && userId) { + if (isUUID(tenantId) && isUUID(userId)) { return `${tenantId}:${userId}`; } - if (tenantId) { + if (isUUID(tenantId)) { return `${tenantId}`; } return 'base'; diff --git a/packages/server/src/db/NileInstance.ts b/packages/server/src/db/NileInstance.ts index 190ff52b..dc9b202b 100644 --- a/packages/server/src/db/NileInstance.ts +++ b/packages/server/src/db/NileInstance.ts @@ -7,6 +7,7 @@ import { AfterCreate } from '../types'; import Logger from '../utils/Logger'; import { createProxyForPool } from './PoolProxy'; +import { isUUID } from './isUUID'; class NileDatabase { pool: Pool; @@ -95,9 +96,9 @@ function makeAfterCreate(config: Config): AfterCreate { done(error, conn); }); - if (config.tenantId) { + if (isUUID(config.tenantId)) { const query = [`SET nile.tenant_id = '${config.tenantId}'`]; - if (config.userId) { + if (isUUID(config.userId)) { if (!config.tenantId) { warn('A user id cannot be set in context without a tenant id'); } @@ -106,10 +107,10 @@ function makeAfterCreate(config: Config): AfterCreate { // in this example we use pg driver's connection API conn.query(query.join(';'), function (err: Error) { - if (config.tenantId) { + if (query.length === 1) { info('[tenant id]', config.tenantId); } - if (config.userId) { + if (query.length === 2) { info('[user id]', config.userId); } done(err, conn); diff --git a/packages/server/src/db/isUUID.ts b/packages/server/src/db/isUUID.ts new file mode 100644 index 00000000..7f572faf --- /dev/null +++ b/packages/server/src/db/isUUID.ts @@ -0,0 +1,8 @@ +export function isUUID(value: string | null | undefined) { + if (!value) { + return false; + } + const regex = + /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + return regex.test(value); +} diff --git a/packages/server/src/tenants/index.ts b/packages/server/src/tenants/index.ts index 4cda0ae2..1d48f83a 100644 --- a/packages/server/src/tenants/index.ts +++ b/packages/server/src/tenants/index.ts @@ -1,5 +1,5 @@ import { Config } from '../utils/Config'; -import Requester, { NileRequest, NileResponse } from '../utils/Requester'; +import Requester, { NileRequest } from '../utils/Requester'; export interface Tenant { id: string; @@ -34,30 +34,45 @@ export default class Tenants extends Config { } createTenant = async ( - req: NileRequest<{ name: string }>, + req: NileRequest<{ name: string }> | Headers | string, init?: RequestInit - ): NileResponse => { + ): Promise => { + let _req; + if (typeof req === 'string') { + _req = new Request(`${this.api.basePath}${this.tenantsUrl}`, { + body: JSON.stringify({ name: req }), + method: 'POST', + }); + } else { + _req = req; + } const _requester = new Requester(this); const _init = this.handleHeaders(init); - return _requester.post(req, this.tenantsUrl, _init); + return _requester.post(_req, this.tenantsUrl, _init); }; getTenant = async ( - req: NileRequest, + req: NileRequest<{ id: string }> | Headers | string | void, init?: RequestInit - ): NileResponse => { + ): Promise => { + if (typeof req === 'string') { + this.tenantId = req; + } const _requester = new Requester(this); const _init = this.handleHeaders(init); - return _requester.get(req, this.tenantUrl, _init); + return _requester.get(req, this.tenantUrl, _init); }; get tenantListUrl() { return `/users/${this.userId ?? '{userId}'}/tenants`; } - listTenants = async (req: NileRequest, init?: RequestInit) => { + listTenants = async ( + req: NileRequest | Headers, + init?: RequestInit + ): Promise => { const _requester = new Requester(this); const _init = this.handleHeaders(init); - return _requester.get(req, this.tenantListUrl, _init); + return _requester.get(req, this.tenantListUrl, _init); }; } diff --git a/packages/server/src/users/index.ts b/packages/server/src/users/index.ts index 7ea6474e..b6c6d98b 100644 --- a/packages/server/src/users/index.ts +++ b/packages/server/src/users/index.ts @@ -1,5 +1,5 @@ import { Config } from '../utils/Config'; -import Requester, { NileRequest, NileResponse } from '../utils/Requester'; +import Requester, { NileRequest } from '../utils/Requester'; export interface CreateBasicUserRequest { email: string; @@ -65,7 +65,7 @@ export default class Users extends Config { createUser = async ( req: NileRequest, init?: RequestInit - ): NileResponse => { + ): Promise => { const _requester = new Requester(this); const _init = this.handleHeaders(init); @@ -76,7 +76,7 @@ export default class Users extends Config { userId: string, req: NileRequest, init?: RequestInit - ): NileResponse => { + ): Promise => { const _requester = new Requester(this); const _init = this.handleHeaders(init); return await _requester.put(req, `${this.usersUrl}/${userId}`, _init); @@ -85,7 +85,7 @@ export default class Users extends Config { listUsers = async ( req: NileRequest | Headers, init?: RequestInit - ): NileResponse => { + ): Promise => { const _requester = new Requester(this); const _init = this.handleHeaders(init); return await _requester.get(req, this.tenantUsersUrl, _init); @@ -94,7 +94,7 @@ export default class Users extends Config { linkUser = async ( req: NileRequest<{ id: string }> | Headers, init?: RequestInit - ): NileResponse => { + ): Promise => { const _requester = new Requester(this); const _init = this.handleHeaders(init); return await _requester.put(req, this.tenantUsersUrl, _init); @@ -122,7 +122,7 @@ export default class Users extends Config { unlinkUser = async ( req: NileRequest<{ id: string }> | Headers, init?: RequestInit - ): NileResponse => { + ): Promise => { const _requester = new Requester(this); const userId = await this.getUserId(req); const _init = this.handleHeaders(init); @@ -138,9 +138,9 @@ export default class Users extends Config { } me = async ( - req: NileRequest, + req: NileRequest | Headers, init?: RequestInit - ): NileResponse => { + ): Promise => { const _requester = new Requester(this); const _init = this.handleHeaders(init); return await _requester.get(req, this.meUrl, _init); diff --git a/packages/server/src/utils/Requester/index.ts b/packages/server/src/utils/Requester/index.ts index b6f70170..bc5d60df 100644 --- a/packages/server/src/utils/Requester/index.ts +++ b/packages/server/src/utils/Requester/index.ts @@ -32,7 +32,7 @@ export default class Requester extends Config { } /** - * three optios here + * three options here * 1) pass in headers for a server side request * 2) pass in the payload that matches the api * 3) pass in the request object sent by a browser @@ -96,35 +96,51 @@ export default class Requester extends Config { return await this.rawRequest(method, url, _init, body); } - post = async ( + async post( req: T | Headers, url: string, init?: RequestInit - ): Promise => { - return await this.request('POST', url, req, init); - }; + ): Promise { + const response = await this.request('POST', url, req, init); + if (response && response.status >= 200 && response.status < 300) { + return response.json(); + } + return response; + } - get = async ( + async get( req: T | Headers, url: string, init?: RequestInit - ): Promise => { - return await this.request('GET', url, req, init); - }; + ): Promise { + const response = await this.request('GET', url, req, init); + if (response && response.status >= 200 && response.status < 300) { + return response.json(); + } + return response; + } - put = async ( + async put( req: T | Headers, url: string, init?: RequestInit - ): Promise => { - return await this.request('PUT', url, req, init); - }; + ): Promise { + const response = await this.request('PUT', url, req, init); + if (response && response.status >= 200 && response.status < 300) { + return response.json(); + } + return response; + } - delete = async ( + async delete( req: T | Headers, url: string, init?: RequestInit - ): Promise => { - return await this.request('DELETE', url, req, init); - }; + ): Promise { + const response = await this.request('DELETE', url, req, init); + if (response && response.status >= 200 && response.status < 300) { + return response.json(); + } + return response; + } } diff --git a/packages/server/src/utils/Requester/types.ts b/packages/server/src/utils/Requester/types.ts index 31a18956..4c4b2dbc 100644 --- a/packages/server/src/utils/Requester/types.ts +++ b/packages/server/src/utils/Requester/types.ts @@ -94,4 +94,4 @@ export interface APIError { statusCode: number; } -export type NileResponse = Promise>; +export type NileResponse = Promise>; diff --git a/packages/server/src/utils/Server/index.ts b/packages/server/src/utils/Server/index.ts index e2c93608..0b28cfe2 100644 --- a/packages/server/src/utils/Server/index.ts +++ b/packages/server/src/utils/Server/index.ts @@ -6,5 +6,5 @@ export const getServerId = (config: ServerConfig) => { return makeServerId(cfg); }; export const makeServerId = (config: Config) => { - return Buffer.from(JSON.stringify(config), 'base64').toString(); + return Buffer.from(JSON.stringify(config), 'utf8').toString('base64'); }; diff --git a/packages/server/test/integration/integration.test.ts b/packages/server/test/integration/integration.test.ts index 6c4ef74c..24ae8f13 100644 --- a/packages/server/test/integration/integration.test.ts +++ b/packages/server/test/integration/integration.test.ts @@ -116,9 +116,8 @@ describe.skip('api integration', () => { await handlers.DELETE(deleteReq); const users = await nile.api.users.listUsers(tenantUsersReq); - const usersBody = await new Response(users.body).json(); - expect(usersBody).toEqual(tenantUsersJson); + expect(users).toEqual(tenantUsersJson); }); test('does api calls for the api sdk', async () => { const nile = new Server(config); @@ -131,15 +130,8 @@ describe.skip('api integration', () => { nile.tenantId = tenantId; const tenantUsers = await nile.api.users.listUsers(); - expect(tenantUsers.status).toEqual(200); - - const linkedUser = await nile.api.users.linkUser({ id: deleteUserId }); - expect(linkedUser.status).toEqual(201); - - const unlinkedUser = await nile.api.users.unlinkUser({ id: deleteUserId }); - expect(unlinkedUser.status).toEqual(204); const users = await nile.api.users.listUsers(); - expect(await users.json()).toEqual(await tenantUsers.json()); + expect(users).toEqual(tenantUsers); }); });