Add ability to configure rpc services. Needs further testing
This commit is contained in:
parent
fbd7a2229b
commit
67e3da3246
@ -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}
|
||||
|
||||
@ -10,33 +10,27 @@ import {Router} from 'react-router-dom'
|
||||
import {Store} from 'redux'
|
||||
import {createBrowserHistory} from 'history'
|
||||
|
||||
export interface IClientRendererParams<A extends Action, D extends IAPIDef> {
|
||||
readonly RootComponent: React.ComponentType<{
|
||||
config: IClientConfig,
|
||||
http: IHTTPClient<D>
|
||||
}>,
|
||||
export interface IClientRendererParams<Props> {
|
||||
readonly RootComponent: React.ComponentType<Props>
|
||||
readonly target?: HTMLElement
|
||||
readonly hydrate: boolean // TODO make this better
|
||||
}
|
||||
|
||||
export class ClientRenderer<A extends Action, D extends IAPIDef>
|
||||
implements IRenderer {
|
||||
constructor(readonly params: IClientRendererParams<A, D>) {}
|
||||
export class ClientRenderer<Props>
|
||||
implements IRenderer<Props> {
|
||||
constructor(readonly params: IClientRendererParams<Props>) {}
|
||||
|
||||
render<State>(
|
||||
url: string,
|
||||
store: Store<State>,
|
||||
config = (window as any).__APP_CONFIG__ as IClientConfig,
|
||||
props: Props,
|
||||
config: IClientConfig,
|
||||
) {
|
||||
const {
|
||||
RootComponent,
|
||||
target = document.getElementById('container'),
|
||||
} = this.params
|
||||
|
||||
const http = new HTTPClient<D>(config.baseUrl + '/api', {
|
||||
'x-csrf-token': config.csrfToken,
|
||||
})
|
||||
|
||||
const history = createBrowserHistory({
|
||||
basename: config.baseUrl,
|
||||
})
|
||||
@ -45,7 +39,7 @@ export class ClientRenderer<A extends Action, D extends IAPIDef>
|
||||
ReactDOM.hydrate(
|
||||
<Provider store={store}>
|
||||
<Router history={history}>
|
||||
<RootComponent config={config} http={http} />
|
||||
<RootComponent {...props} />
|
||||
</Router>
|
||||
</Provider>,
|
||||
target,
|
||||
@ -54,7 +48,7 @@ export class ClientRenderer<A extends Action, D extends IAPIDef>
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<Router history={history}>
|
||||
<RootComponent config={config} http={http} />
|
||||
<RootComponent {...props} />
|
||||
</Router>
|
||||
</Provider>,
|
||||
target,
|
||||
|
||||
@ -2,10 +2,11 @@ import {IAPIDef} from '@rondo.dev/common'
|
||||
import {IClientConfig} from './IClientConfig'
|
||||
import {Store} from 'redux'
|
||||
|
||||
export interface IRenderer {
|
||||
export interface IRenderer<Props> {
|
||||
render<State>(
|
||||
url: string,
|
||||
store: Store<State>,
|
||||
props: Props,
|
||||
config: IClientConfig,
|
||||
): any
|
||||
}
|
||||
|
||||
@ -11,27 +11,20 @@ import {StaticRouter} from 'react-router-dom'
|
||||
import {Store} from 'redux'
|
||||
import {renderToNodeStream} from 'react-dom/server'
|
||||
|
||||
export class ServerRenderer<A extends Action, D extends IAPIDef>
|
||||
implements IRenderer {
|
||||
export class ServerRenderer<Props> implements IRenderer<Props> {
|
||||
constructor(
|
||||
readonly RootComponent: React.ComponentType<{
|
||||
config: IClientConfig,
|
||||
http: IHTTPClient<D>
|
||||
}>,
|
||||
readonly RootComponent: React.ComponentType<Props>,
|
||||
) {}
|
||||
async render<State>(
|
||||
url: string,
|
||||
store: Store<State>,
|
||||
props: Props,
|
||||
config: IClientConfig,
|
||||
host: string = '',
|
||||
headers: Record<string, string> = {},
|
||||
) {
|
||||
const {RootComponent} = this
|
||||
// TODO set cookie in headers here...
|
||||
const http = new HTTPClient<D>(
|
||||
'http://' + host + config.baseUrl + '/api',
|
||||
headers,
|
||||
)
|
||||
|
||||
const context: StaticRouterContext = {}
|
||||
|
||||
@ -42,7 +35,7 @@ export class ServerRenderer<A extends Action, D extends IAPIDef>
|
||||
location={url}
|
||||
context={context}
|
||||
>
|
||||
<RootComponent config={config} http={http} />
|
||||
<RootComponent {...props} />
|
||||
</StaticRouter>
|
||||
</Provider>
|
||||
)
|
||||
|
||||
@ -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) =>
|
||||
<MemoryRouter initialEntries={historyEntries}>
|
||||
<Component {...props} />
|
||||
|
||||
@ -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<ITeamState> {
|
||||
constructor(protected readonly teamActions: TeamActions) {
|
||||
super()
|
||||
}
|
||||
|
||||
connect<State>(getLocalState: TStateSelector<State, ITeamState>) {
|
||||
const Component = this.wrap(
|
||||
export function configure<State>(
|
||||
teamActions: TeamActions,
|
||||
getLocalState: TStateSelector<State, ITeamState>,
|
||||
) {
|
||||
const Component = pack(
|
||||
getLocalState,
|
||||
state => ({
|
||||
...state,
|
||||
}),
|
||||
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),
|
||||
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(this.teamActions.fetchUsersInTeam, d),
|
||||
bindActionCreators(teamActions.fetchUsersInTeam, d),
|
||||
findUserByEmail:
|
||||
bindActionCreators(this.teamActions.findUserByEmail, d),
|
||||
bindActionCreators(teamActions.findUserByEmail, d),
|
||||
}),
|
||||
TeamManager,
|
||||
)
|
||||
|
||||
return Component
|
||||
}
|
||||
}
|
||||
|
||||
8
packages/common/src/rpc.ts
Normal file
8
packages/common/src/rpc.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import {IContext} from './IContext'
|
||||
import {ITeamService} from './team'
|
||||
import {IUserService} from './user'
|
||||
|
||||
export interface IRPCServices {
|
||||
userService: IUserService
|
||||
teamService: ITeamService
|
||||
}
|
||||
@ -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'
|
||||
@ -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<T>(obj: T): Array<keyof T & string> {
|
||||
return Object.keys(obj) as Array<keyof T & string>
|
||||
}
|
||||
|
||||
type BulkLocalClient<T> = {[K in keyof T & string]: LocalClient<T[K]>}
|
||||
type BulkActions<T> = {[K in keyof T & string]: TReduxed<T[K], K>}
|
||||
type BulkRemoteClient<T> = {[K in keyof T & string]: TAsyncified<T[K]>}
|
||||
export type BulkServices<T, Cx> = {
|
||||
[K in keyof T & string]: Contextual<T[K], Cx>
|
||||
}
|
||||
export type BulkLocalClient<T> = {[K in keyof T & string]: LocalClient<T[K]>}
|
||||
export type BulkActions<T> = {[K in keyof T & string]: TReduxed<T[K], K>}
|
||||
export type BulkClient<T> = {[K in keyof T & string]: TAsyncified<T[K]>}
|
||||
|
||||
function bulkCreate<T, R>(
|
||||
src: T,
|
||||
@ -22,7 +25,7 @@ function bulkCreate<T, R>(
|
||||
}, {} as any)
|
||||
}
|
||||
|
||||
export function bulkCreateLocalClient<T, Cx>(
|
||||
export function bulkCreateLocalClient<Cx, T extends Contextual<{}, Cx>>(
|
||||
src: T,
|
||||
context: Cx,
|
||||
): BulkLocalClient<T> {
|
||||
@ -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'
|
||||
|
||||
@ -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<rpc.TeamService>())
|
||||
.addService('/user',
|
||||
new rpc.UserService(this.database),
|
||||
keys<rpc.UserService>())
|
||||
.router(),
|
||||
)
|
||||
// Override this method
|
||||
}
|
||||
|
||||
protected configureApiErrorHandling(router: express.Router) {
|
||||
|
||||
@ -18,7 +18,7 @@ export class TeamService implements RPC<t.ITeamService> {
|
||||
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<t.ITeamService> {
|
||||
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<t.ITeamService> {
|
||||
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<t.ITeamService> {
|
||||
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<t.ITeamService> {
|
||||
})
|
||||
.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<t.ITeamService> {
|
||||
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<t.ITeamService> {
|
||||
.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<t.ITeamService> {
|
||||
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<t.ITeamService> {
|
||||
}
|
||||
}
|
||||
|
||||
protected createFindUserInTeamQuery() {
|
||||
protected _createFindUserInTeamQuery() {
|
||||
return this.db.getRepository(UserTeam)
|
||||
.createQueryBuilder('ut')
|
||||
.select('ut')
|
||||
|
||||
@ -13,7 +13,7 @@ const MIN_PASSWORD_LENGTH = 10
|
||||
export class UserService implements RPC<u.IUserService> {
|
||||
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<u.IUserService> {
|
||||
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<u.IUserService> {
|
||||
}
|
||||
}
|
||||
|
||||
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<u.IUserService> {
|
||||
}
|
||||
}
|
||||
|
||||
protected async hash(password: string): Promise<string> {
|
||||
protected async _hash(password: string): Promise<string> {
|
||||
return hash(password, SALT_ROUNDS)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user