diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 2c11036..8724035 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -9,5 +9,7 @@ export * from './middleware' export * from './redux' export * from './renderer' export * from './store' -export * from './team' export * from './test-utils' + +import * as team from './team' +export {team} diff --git a/packages/client/src/renderer/ClientRenderer.tsx b/packages/client/src/renderer/ClientRenderer.tsx index 0c2ad81..be1e742 100644 --- a/packages/client/src/renderer/ClientRenderer.tsx +++ b/packages/client/src/renderer/ClientRenderer.tsx @@ -10,33 +10,27 @@ import {Router} from 'react-router-dom' import {Store} from 'redux' import {createBrowserHistory} from 'history' -export interface IClientRendererParams { - readonly RootComponent: React.ComponentType<{ - config: IClientConfig, - http: IHTTPClient - }>, +export interface IClientRendererParams { + readonly RootComponent: React.ComponentType readonly target?: HTMLElement readonly hydrate: boolean // TODO make this better } -export class ClientRenderer - implements IRenderer { - constructor(readonly params: IClientRendererParams) {} +export class ClientRenderer + implements IRenderer { + constructor(readonly params: IClientRendererParams) {} render( url: string, store: Store, - config = (window as any).__APP_CONFIG__ as IClientConfig, + props: Props, + config: IClientConfig, ) { const { RootComponent, target = document.getElementById('container'), } = this.params - const http = new HTTPClient(config.baseUrl + '/api', { - 'x-csrf-token': config.csrfToken, - }) - const history = createBrowserHistory({ basename: config.baseUrl, }) @@ -45,7 +39,7 @@ export class ClientRenderer ReactDOM.hydrate( - + , target, @@ -54,7 +48,7 @@ export class ClientRenderer ReactDOM.render( - + , target, diff --git a/packages/client/src/renderer/IRenderer.ts b/packages/client/src/renderer/IRenderer.ts index 5b40fd8..8afe80d 100644 --- a/packages/client/src/renderer/IRenderer.ts +++ b/packages/client/src/renderer/IRenderer.ts @@ -2,10 +2,11 @@ import {IAPIDef} from '@rondo.dev/common' import {IClientConfig} from './IClientConfig' import {Store} from 'redux' -export interface IRenderer { +export interface IRenderer { render( url: string, store: Store, + props: Props, config: IClientConfig, ): any } diff --git a/packages/client/src/renderer/ServerRenderer.tsx b/packages/client/src/renderer/ServerRenderer.tsx index 68664af..df3a6fb 100644 --- a/packages/client/src/renderer/ServerRenderer.tsx +++ b/packages/client/src/renderer/ServerRenderer.tsx @@ -11,27 +11,20 @@ import {StaticRouter} from 'react-router-dom' import {Store} from 'redux' import {renderToNodeStream} from 'react-dom/server' -export class ServerRenderer - implements IRenderer { +export class ServerRenderer implements IRenderer { constructor( - readonly RootComponent: React.ComponentType<{ - config: IClientConfig, - http: IHTTPClient - }>, + readonly RootComponent: React.ComponentType, ) {} async render( url: string, store: Store, + props: Props, config: IClientConfig, host: string = '', headers: Record = {}, ) { const {RootComponent} = this // TODO set cookie in headers here... - const http = new HTTPClient( - 'http://' + host + config.baseUrl + '/api', - headers, - ) const context: StaticRouterContext = {} @@ -42,7 +35,7 @@ export class ServerRenderer location={url} context={context} > - + ) diff --git a/packages/client/src/team/TeamConnector.test.tsx b/packages/client/src/team/TeamConnector.test.tsx index 8c10288..c6c472d 100644 --- a/packages/client/src/team/TeamConnector.test.tsx +++ b/packages/client/src/team/TeamConnector.test.tsx @@ -40,10 +40,7 @@ describe('TeamConnector', () => { reducers: {Team: Feature.Team}, select: state => state.Team, }) - .withComponent(select => - new Feature - .TeamConnector(teamActions) - .connect(select)) + .withComponent(select => Feature.configure(teamActions, select)) .withJSX((Component, props) => diff --git a/packages/client/src/team/TeamConnector.ts b/packages/client/src/team/TeamConnector.ts index 0c8b12b..e6be98e 100644 --- a/packages/client/src/team/TeamConnector.ts +++ b/packages/client/src/team/TeamConnector.ts @@ -1,37 +1,32 @@ import {Connector} from '../redux/Connector' -import {TStateSelector} from '../redux' +import {pack, TStateSelector} from '../redux' import {ITeamState} from './TeamReducer' import {TeamActions} from './TeamActions' import {TeamManager} from './TeamManager' import {bindActionCreators} from 'redux' import {withRouter} from 'react-router-dom' -export class TeamConnector extends Connector { - constructor(protected readonly teamActions: TeamActions) { - super() - } +export function configure( + teamActions: TeamActions, + getLocalState: TStateSelector, +) { + const Component = pack( + getLocalState, + state => ({...state}), + d => ({ + addUser: bindActionCreators(teamActions.addUser, d), + removeUser: bindActionCreators(teamActions.removeUser, d), + createTeam: bindActionCreators(teamActions.createTeam, d), + updateTeam: bindActionCreators(teamActions.updateTeam, d), + removeTeam: bindActionCreators(teamActions.removeTeam, d), + fetchMyTeams: bindActionCreators(teamActions.fetchMyTeams, d), + fetchUsersInTeam: + bindActionCreators(teamActions.fetchUsersInTeam, d), + findUserByEmail: + bindActionCreators(teamActions.findUserByEmail, d), + }), + TeamManager, + ) - connect(getLocalState: TStateSelector) { - const Component = this.wrap( - getLocalState, - state => ({ - ...state, - }), - d => ({ - addUser: bindActionCreators(this.teamActions.addUser, d), - removeUser: bindActionCreators(this.teamActions.removeUser, d), - createTeam: bindActionCreators(this.teamActions.createTeam, d), - updateTeam: bindActionCreators(this.teamActions.updateTeam, d), - removeTeam: bindActionCreators(this.teamActions.removeTeam, d), - fetchMyTeams: bindActionCreators(this.teamActions.fetchMyTeams, d), - fetchUsersInTeam: - bindActionCreators(this.teamActions.fetchUsersInTeam, d), - findUserByEmail: - bindActionCreators(this.teamActions.findUserByEmail, d), - }), - TeamManager, - ) - - return Component - } + return Component } diff --git a/packages/common/src/rpc.ts b/packages/common/src/rpc.ts new file mode 100644 index 0000000..1d3947a --- /dev/null +++ b/packages/common/src/rpc.ts @@ -0,0 +1,8 @@ +import {IContext} from './IContext' +import {ITeamService} from './team' +import {IUserService} from './user' + +export interface IRPCServices { + userService: IUserService + teamService: ITeamService +} diff --git a/packages/jsonrpc/src/util.test.ts b/packages/jsonrpc/src/bulk.test.ts similarity index 98% rename from packages/jsonrpc/src/util.test.ts rename to packages/jsonrpc/src/bulk.test.ts index f3a3dc3..ed8c7e2 100644 --- a/packages/jsonrpc/src/util.test.ts +++ b/packages/jsonrpc/src/bulk.test.ts @@ -1,4 +1,4 @@ -import * as util from './util' +import * as util from './bulk' import express from 'express' import {Contextual} from './types' import {jsonrpc} from './express' diff --git a/packages/jsonrpc/src/util.ts b/packages/jsonrpc/src/bulk.ts similarity index 70% rename from packages/jsonrpc/src/util.ts rename to packages/jsonrpc/src/bulk.ts index 7c6ad8b..3643e89 100644 --- a/packages/jsonrpc/src/util.ts +++ b/packages/jsonrpc/src/bulk.ts @@ -1,5 +1,5 @@ import {IJSONRPCReturnType} from './express' -import {TAsyncified, TReduxed} from './types' +import {Contextual, TAsyncified, TReduxed} from './types' import {createActions} from './redux' import {createLocalClient, LocalClient} from './local' @@ -7,9 +7,12 @@ function keys(obj: T): Array { return Object.keys(obj) as Array } -type BulkLocalClient = {[K in keyof T & string]: LocalClient} -type BulkActions = {[K in keyof T & string]: TReduxed} -type BulkRemoteClient = {[K in keyof T & string]: TAsyncified} +export type BulkServices = { + [K in keyof T & string]: Contextual +} +export type BulkLocalClient = {[K in keyof T & string]: LocalClient} +export type BulkActions = {[K in keyof T & string]: TReduxed} +export type BulkClient = {[K in keyof T & string]: TAsyncified} function bulkCreate( src: T, @@ -22,7 +25,7 @@ function bulkCreate( }, {} as any) } -export function bulkCreateLocalClient( +export function bulkCreateLocalClient>( src: T, context: Cx, ): BulkLocalClient { diff --git a/packages/jsonrpc/src/index.ts b/packages/jsonrpc/src/index.ts index f5b70ac..ca092c0 100644 --- a/packages/jsonrpc/src/index.ts +++ b/packages/jsonrpc/src/index.ts @@ -1,3 +1,4 @@ +export * from './bulk' export * from './ensure' export * from './error' export * from './express' @@ -6,4 +7,3 @@ export * from './local' export * from './redux' export * from './remote' export * from './types' -export * from './util' diff --git a/packages/server/src/application/Application.ts b/packages/server/src/application/Application.ts index 814dd20..43fd9c0 100644 --- a/packages/server/src/application/Application.ts +++ b/packages/server/src/application/Application.ts @@ -12,7 +12,7 @@ import {IApplication} from './IApplication' import {IConfig} from './IConfig' import {IDatabase} from '../database/IDatabase' import {ILogger} from '../logger/ILogger' -import {IRoutes} from '@rondo.dev/common' +import {IRoutes, IContext} from '@rondo.dev/common' import {IServices} from './IServices' import {ITransactionManager} from '../database/ITransactionManager' import {loggerFactory} from '../logger' @@ -112,9 +112,13 @@ export class Application implements IApplication { ).handle) } + protected getContext(req: express.Request): IContext { + return {user: req.user} + } + protected jsonrpc() { return jsonrpc( - req => ({user: req.user}), + req => this.getContext(req), this.getApiLogger(), (path, service, callback) => this .database @@ -124,17 +128,7 @@ export class Application implements IApplication { } protected configureRPC(router: express.Router) { - router.use( - '/rpc', - this.jsonrpc() - .addService('/team', - new rpc.TeamService(this.database, this.services.userPermissions), - keys()) - .addService('/user', - new rpc.UserService(this.database), - keys()) - .router(), - ) + // Override this method } protected configureApiErrorHandling(router: express.Router) { diff --git a/packages/server/src/rpc/TeamService.ts b/packages/server/src/rpc/TeamService.ts index ca31728..59c5a66 100644 --- a/packages/server/src/rpc/TeamService.ts +++ b/packages/server/src/rpc/TeamService.ts @@ -18,7 +18,7 @@ export class TeamService implements RPC { protected readonly permissions: IUserPermissions, ) {} - async create(params: t.ITeamCreateParams, context: IContext) { + async create(context: IContext, params: t.ITeamCreateParams) { const userId = context.user!.id const name = trim(params.name) @@ -32,17 +32,17 @@ export class TeamService implements RPC { userId, }) - await this.addUser({ + await this.addUser(context, { teamId: team.id, userId, // ADMIN role roleId: 1, - }, context) + }) return team } - async remove({id}: t.ITeamRemoveParams, context: IContext) { + async remove(context: IContext, {id}: t.ITeamRemoveParams) { const userId = context.user!.id await this.permissions.belongsToTeam({ @@ -59,7 +59,7 @@ export class TeamService implements RPC { return {id} } - async update({id, name}: t.ITeamUpdateParams, context: IContext) { + async update(context: IContext, {id, name}: t.ITeamUpdateParams) { const userId = context.user!.id await this.permissions.belongsToTeam({ @@ -74,15 +74,15 @@ export class TeamService implements RPC { name, }) - return (await this.findOne(id))! + return (await this.findOne(context, id))! } - async addUser(params: t.ITeamAddUserParams, context: IContext) { + async addUser(context: IContext, params: t.ITeamAddUserParams) { const {userId, teamId, roleId} = params await this.db.getRepository(UserTeam) .save({userId, teamId, roleId}) - const userTeam = await this.createFindUserInTeamQuery() + const userTeam = await this._createFindUserInTeamQuery() .where({ userId, teamId, @@ -90,10 +90,10 @@ export class TeamService implements RPC { }) .getOne() - return this.mapUserInTeam(userTeam!) + return this._mapUserInTeam(userTeam!) } - async removeUser(params: t.ITeamAddUserParams, context: IContext) { + async removeUser(context: IContext, params: t.ITeamAddUserParams) { const {teamId, userId, roleId} = params await this.permissions.belongsToTeam({ @@ -108,7 +108,7 @@ export class TeamService implements RPC { return {teamId, userId, roleId} } - async findOne(id: number) { + async findOne(context: IContext, id: number) { return this.db.getRepository(Team).findOne(id) } @@ -123,7 +123,7 @@ export class TeamService implements RPC { .getMany() } - async findUsers(teamId: number, context: IContext) { + async findUsers(context: IContext, teamId: number) { const userId = context.user!.id await this.permissions.belongsToTeam({ @@ -131,16 +131,16 @@ export class TeamService implements RPC { userId, }) - const userTeams = await this.createFindUserInTeamQuery() + const userTeams = await this._createFindUserInTeamQuery() .where('ut.teamId = :teamId', { teamId, }) .getMany() - return userTeams.map(this.mapUserInTeam) + return userTeams.map(this._mapUserInTeam) } - protected mapUserInTeam(ut: UserTeam): IUserInTeam { + protected _mapUserInTeam(ut: UserTeam): IUserInTeam { return { teamId: ut.teamId, userId: ut.userId, @@ -150,7 +150,7 @@ export class TeamService implements RPC { } } - protected createFindUserInTeamQuery() { + protected _createFindUserInTeamQuery() { return this.db.getRepository(UserTeam) .createQueryBuilder('ut') .select('ut') diff --git a/packages/server/src/rpc/UserService.ts b/packages/server/src/rpc/UserService.ts index 5e127a0..72e25e7 100644 --- a/packages/server/src/rpc/UserService.ts +++ b/packages/server/src/rpc/UserService.ts @@ -13,7 +13,7 @@ const MIN_PASSWORD_LENGTH = 10 export class UserService implements RPC { constructor(protected readonly db: IDatabase) {} - async changePassword(params: u.IChangePasswordParams, context: IContext) { + async changePassword(context: IContext, params: u.IChangePasswordParams) { const userId = context.user!.id const {oldPassword, newPassword} = params const userRepository = this.db.getRepository(User) @@ -27,12 +27,12 @@ export class UserService implements RPC { if (!(user && isValid)) { throw createError(400, 'Passwords do not match') } - const password = await this.hash(newPassword) + const password = await this._hash(newPassword) await userRepository .update(userId, { password }) } - async findOne(id: number) { + async findOne(context: IContext, id: number) { const user = await this.db.getRepository(User).findOne(id, { relations: ['emails'], }) @@ -49,7 +49,7 @@ export class UserService implements RPC { } } - async findUserByEmail(email: string) { + async findUserByEmail(context: IContext, email: string) { const userEmail = await this.db.getRepository(UserEmail) .findOne({ email }, { relations: ['user'], @@ -69,7 +69,7 @@ export class UserService implements RPC { } } - protected async hash(password: string): Promise { + protected async _hash(password: string): Promise { return hash(password, SALT_ROUNDS) } }