Add UserService (RPC version)
This commit is contained in:
parent
cd5ff9b5da
commit
7434c9fb42
@ -22,5 +22,8 @@ export * from './without'
|
||||
import * as team from './team'
|
||||
export {team}
|
||||
|
||||
import * as user from './user'
|
||||
export {user}
|
||||
|
||||
import * as entities from './entities'
|
||||
export {entities}
|
||||
|
||||
20
packages/common/src/user.ts
Normal file
20
packages/common/src/user.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import {ICredentials} from './ICredentials'
|
||||
import {IUser} from './IUser'
|
||||
import * as e from './entities'
|
||||
|
||||
export interface IChangePasswordParams {
|
||||
oldPassword: string
|
||||
newPassword: string
|
||||
}
|
||||
|
||||
export interface ICreateUserParams extends ICredentials {
|
||||
firstName: string
|
||||
lastName: string
|
||||
}
|
||||
|
||||
export interface IUserService {
|
||||
changePassword(params: IChangePasswordParams): Promise<void>
|
||||
// validateCredentials(credentials: ICredentials): Promise<e.User | undefined>
|
||||
findOne(id: number): Promise<IUser | undefined>
|
||||
findUserByEmail(email: string): Promise<IUser | undefined>
|
||||
}
|
||||
@ -12,11 +12,20 @@ import {
|
||||
|
||||
export type TGetContext<Context> = (req: Request) => Context
|
||||
|
||||
export interface IJSONRPCReturnType {
|
||||
addService<T, F extends FunctionPropertyNames<T>>(
|
||||
path: string,
|
||||
service: T,
|
||||
methods: F[],
|
||||
): IJSONRPCReturnType,
|
||||
router(): Router
|
||||
}
|
||||
|
||||
export function jsonrpc<Context>(
|
||||
getContext: TGetContext<Context>,
|
||||
logger: ILogger,
|
||||
idempotentMethodRegex = IDEMPOTENT_METHOD_REGEX,
|
||||
) {
|
||||
): IJSONRPCReturnType {
|
||||
|
||||
const handleError: ErrorRequestHandler = (err, req, res, next) => {
|
||||
logger.error('JSON-RPC Error: %s', err.stack)
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import * as middleware from '../middleware'
|
||||
import * as routes from '../routes'
|
||||
import * as rpc from '../rpc'
|
||||
import * as services from '../services'
|
||||
import * as team from '../team'
|
||||
import * as user from '../user'
|
||||
import cookieParser from 'cookie-parser'
|
||||
import express from 'express'
|
||||
import {keys} from 'ts-transformer-keys'
|
||||
import {AsyncRouter, TransactionalRouter} from '../router'
|
||||
import {IApplication} from './IApplication'
|
||||
import {IConfig} from './IConfig'
|
||||
@ -16,6 +18,7 @@ import {ITransactionManager} from '../database/ITransactionManager'
|
||||
import {loggerFactory} from '../logger'
|
||||
import {ILoggerFactory} from '@rondo.dev/logger'
|
||||
import {json} from 'body-parser'
|
||||
import {jsonrpc} from '@rondo.dev/jsonrpc'
|
||||
|
||||
export class Application implements IApplication {
|
||||
readonly transactionManager: ITransactionManager
|
||||
@ -106,6 +109,21 @@ export class Application implements IApplication {
|
||||
this.services.userPermissions,
|
||||
this.createTransactionalRouter(),
|
||||
).handle)
|
||||
|
||||
router.use(
|
||||
'/rpc',
|
||||
jsonrpc(
|
||||
req => ({user: req.user}),
|
||||
this.getApiLogger(),
|
||||
)
|
||||
.addService('/teamService',
|
||||
new rpc.TeamService(this.database, this.services.userPermissions),
|
||||
keys<rpc.TeamService>())
|
||||
.addService('/userService',
|
||||
new rpc.UserService(this.database),
|
||||
keys<rpc.UserService>())
|
||||
.router(),
|
||||
)
|
||||
}
|
||||
|
||||
protected configureApiErrorHandling(router: express.Router) {
|
||||
|
||||
10
packages/server/src/rpc/RPC.ts
Normal file
10
packages/server/src/rpc/RPC.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { IContext } from '@rondo.dev/common'
|
||||
import { Contextual, ensure } from '@rondo.dev/jsonrpc'
|
||||
|
||||
export { IContext }
|
||||
export type RPC<Service> = Contextual<Service, IContext>
|
||||
|
||||
export const ensureLoggedIn = ensure<IContext>(
|
||||
c => !!c.user && !!c.user.id,
|
||||
'You must be logged in to perform this action',
|
||||
)
|
||||
@ -5,15 +5,14 @@ import {UserTeam} from '../entities/UserTeam'
|
||||
import {IUserPermissions} from '../user/IUserPermissions'
|
||||
import {
|
||||
trim,
|
||||
IContext,
|
||||
entities as e,
|
||||
team as t,
|
||||
IUserInTeam,
|
||||
} from '@rondo.dev/common'
|
||||
import {Contextual} from '@rondo.dev/jsonrpc'
|
||||
import { ensureLoggedIn, IContext, RPC } from './RPC'
|
||||
|
||||
// TODO ensureLoggedIn
|
||||
export class TeamService2 implements Contextual<t.ITeamService, IContext> {
|
||||
@ensureLoggedIn
|
||||
export class TeamService implements RPC<t.ITeamService> {
|
||||
constructor(
|
||||
protected readonly db: IDatabase,
|
||||
protected readonly permissions: IUserPermissions,
|
||||
75
packages/server/src/rpc/UserService.ts
Normal file
75
packages/server/src/rpc/UserService.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { user as u } from '@rondo.dev/common'
|
||||
import { compare, hash } from 'bcrypt'
|
||||
import createError from 'http-errors'
|
||||
import { IDatabase } from '../database/IDatabase'
|
||||
import { User } from '../entities/User'
|
||||
import { UserEmail } from '../entities/UserEmail'
|
||||
import { ensureLoggedIn, IContext, RPC } from './RPC'
|
||||
|
||||
const SALT_ROUNDS = 10
|
||||
const MIN_PASSWORD_LENGTH = 10
|
||||
|
||||
@ensureLoggedIn
|
||||
export class UserService implements RPC<u.IUserService> {
|
||||
constructor(protected readonly db: IDatabase) {}
|
||||
|
||||
async changePassword(params: u.IChangePasswordParams, context: IContext) {
|
||||
const userId = context.user!.id
|
||||
const {oldPassword, newPassword} = params
|
||||
const userRepository = this.db.getRepository(User)
|
||||
const user = await userRepository
|
||||
.createQueryBuilder('user')
|
||||
.select('user')
|
||||
.addSelect('user.password')
|
||||
.whereInIds([ userId ])
|
||||
.getOne()
|
||||
const isValid = await compare(oldPassword, user ? user.password! : '')
|
||||
if (!(user && isValid)) {
|
||||
throw createError(400, 'Passwords do not match')
|
||||
}
|
||||
const password = await this.hash(newPassword)
|
||||
await userRepository
|
||||
.update(userId, { password })
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const user = await this.db.getRepository(User).findOne(id, {
|
||||
relations: ['emails'],
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.emails[0] ? user.emails[0].email : '',
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
}
|
||||
}
|
||||
|
||||
async findUserByEmail(email: string) {
|
||||
const userEmail = await this.db.getRepository(UserEmail)
|
||||
.findOne({ email }, {
|
||||
relations: ['user'],
|
||||
})
|
||||
|
||||
if (!userEmail) {
|
||||
return
|
||||
}
|
||||
|
||||
const user = userEmail.user!
|
||||
|
||||
return {
|
||||
id: userEmail.userId!,
|
||||
username: userEmail.email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
}
|
||||
}
|
||||
|
||||
protected async hash(password: string): Promise<string> {
|
||||
return hash(password, SALT_ROUNDS)
|
||||
}
|
||||
}
|
||||
3
packages/server/src/rpc/index.ts
Normal file
3
packages/server/src/rpc/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './RPC'
|
||||
export * from './TeamService'
|
||||
export * from './UserService'
|
||||
Loading…
x
Reference in New Issue
Block a user