Add packages/services/src/services/TeamService2.ts
A few notes:
1) The context higher-order function will be tedious to define - a lot
more typing than just an extra function argument.
2) As some methods do not require the context function, forgetting to
call it might introduce bugs (await fn will not error out), but
compile checks of the return value type might detect this
Possible solution is to go the GRPC way and allow only a single method
type as a parameter so all server-side method types would look like:
async fn(param: IParam, context: Context) {}
and all client-side method types would look like:
async fn(param: IParam) {}
However, This would deviate from the JSON-RPC standard which allows
multiple arguments to be passed in an array.
Alternatively, context could be passed as a first argument and then
filtered out:
type Arguments<T> = T extends (context: Ctx, ...args: infer A) => infer R
? A
: never
In this case, the type of Ctx would need to be known in advance. Will
have to think about this some more.
This commit is contained in:
parent
a057ca97d5
commit
b617069784
@ -16,3 +16,6 @@ export * from './filterProps'
|
||||
export * from './indexBy'
|
||||
export * from './types'
|
||||
export * from './without'
|
||||
|
||||
import * as team from './team'
|
||||
export {team}
|
||||
|
||||
47
packages/common/src/team.ts
Normal file
47
packages/common/src/team.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import {ITeam} from './ITeam'
|
||||
import {IUserInTeam} from './IUserInTeam'
|
||||
|
||||
export interface ITeamAddUserParams {
|
||||
teamId: number
|
||||
userId: number
|
||||
roleId: number
|
||||
}
|
||||
|
||||
export interface ITeamCreateParams {
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface ITeamRemoveParams {
|
||||
id: number
|
||||
}
|
||||
|
||||
export interface ITeamUpdateParams {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface IContext {
|
||||
userId: number
|
||||
}
|
||||
|
||||
type Contextual<T> = (context: IContext) => Promise<T>
|
||||
|
||||
export interface ITeamService {
|
||||
create(params: ITeamCreateParams): Contextual<ITeam>
|
||||
|
||||
remove(params: ITeamRemoveParams): Contextual<{id: number}>
|
||||
|
||||
update(params: ITeamUpdateParams): Contextual<ITeam>
|
||||
|
||||
addUser(params: ITeamAddUserParams): Contextual<IUserInTeam>
|
||||
|
||||
removeUser(params: ITeamAddUserParams): Contextual<ITeamAddUserParams>
|
||||
|
||||
findOne(id: number): Promise<ITeam | undefined>
|
||||
|
||||
find(userId: number): Contextual<ITeam[]>
|
||||
|
||||
findUsers(teamId: number): Contextual<IUserInTeam[]>
|
||||
|
||||
// TODO add other methods
|
||||
}
|
||||
153
packages/server/src/services/TeamService2.ts
Normal file
153
packages/server/src/services/TeamService2.ts
Normal file
@ -0,0 +1,153 @@
|
||||
import {DB} from '../database/DB'
|
||||
import {Validator} from '../validator'
|
||||
import {Team} from '../entities/Team'
|
||||
import {UserTeam} from '../entities/UserTeam'
|
||||
import {IUserPermissions} from '../user/IUserPermissions'
|
||||
import {
|
||||
trim,
|
||||
team as t,
|
||||
IUserInTeam,
|
||||
} from '@rondo/common'
|
||||
|
||||
type IContext = t.IContext
|
||||
|
||||
export class TeamService2 implements t.ITeamService {
|
||||
constructor(
|
||||
protected readonly db: DB,
|
||||
protected readonly permissions: IUserPermissions,
|
||||
) {}
|
||||
|
||||
create = (params: t.ITeamCreateParams) => async (context: IContext) => {
|
||||
const {userId} = context
|
||||
const name = trim(params.name)
|
||||
|
||||
new Validator({name, userId})
|
||||
.ensure('name')
|
||||
.ensure('userId')
|
||||
.throw()
|
||||
|
||||
const team = await this.db.getRepository(Team).save({
|
||||
name,
|
||||
userId,
|
||||
})
|
||||
|
||||
await this.addUser({
|
||||
teamId: team.id,
|
||||
userId,
|
||||
// ADMIN role
|
||||
roleId: 1,
|
||||
})(context)
|
||||
|
||||
return team
|
||||
}
|
||||
|
||||
remove = ({id}: t.ITeamRemoveParams) => async (context: IContext) => {
|
||||
const {userId} = context
|
||||
|
||||
await this.permissions.belongsToTeam({
|
||||
teamId: id,
|
||||
userId: context.userId,
|
||||
})
|
||||
|
||||
await this.db.getRepository(UserTeam)
|
||||
.delete({teamId: id, userId})
|
||||
|
||||
await this.db.getRepository(Team)
|
||||
.delete(id)
|
||||
|
||||
return {id}
|
||||
}
|
||||
|
||||
update = ({id, name}: t.ITeamUpdateParams) => async (context: IContext) => {
|
||||
await this.permissions.belongsToTeam({
|
||||
teamId: id,
|
||||
userId: context.userId,
|
||||
})
|
||||
|
||||
await this.db.getRepository(Team)
|
||||
.update({
|
||||
id,
|
||||
}, {
|
||||
name,
|
||||
})
|
||||
|
||||
return (await this.findOne(id))!
|
||||
}
|
||||
|
||||
addUser = (params: t.ITeamAddUserParams) => async (context: IContext) => {
|
||||
const {userId, teamId, roleId} = params
|
||||
await this.db.getRepository(UserTeam)
|
||||
.save({userId, teamId, roleId})
|
||||
|
||||
const userTeam = await this.createFindUserInTeamQuery()
|
||||
.where({
|
||||
userId,
|
||||
teamId,
|
||||
roleId,
|
||||
})
|
||||
.getOne()
|
||||
|
||||
return this.mapUserInTeam(userTeam!)
|
||||
}
|
||||
|
||||
removeUser = (params: t.ITeamAddUserParams) => async (context: IContext) => {
|
||||
const {teamId, userId, roleId} = params
|
||||
|
||||
await this.permissions.belongsToTeam({
|
||||
teamId: params.teamId,
|
||||
userId: context.userId,
|
||||
})
|
||||
|
||||
// TODO check if this is the last admin team member
|
||||
await this.db.getRepository(UserTeam)
|
||||
.delete({userId, teamId, roleId})
|
||||
|
||||
return {teamId, userId, roleId}
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
return this.db.getRepository(Team).findOne(id)
|
||||
}
|
||||
|
||||
find = () => async ({userId}: IContext) => {
|
||||
return this.db.getRepository(Team)
|
||||
.createQueryBuilder('team')
|
||||
.select('team')
|
||||
.innerJoin('team.userTeams', 'ut')
|
||||
.where('ut.userId = :userId', {userId})
|
||||
.getMany()
|
||||
}
|
||||
|
||||
findUsers = (teamId: number) => async (context: IContext) => {
|
||||
await this.permissions.belongsToTeam({
|
||||
teamId,
|
||||
userId: context.userId,
|
||||
})
|
||||
|
||||
const userTeams = await this.createFindUserInTeamQuery()
|
||||
.where('ut.teamId = :teamId', {
|
||||
teamId,
|
||||
})
|
||||
.getMany()
|
||||
|
||||
return userTeams.map(this.mapUserInTeam)
|
||||
}
|
||||
|
||||
protected mapUserInTeam(ut: UserTeam): IUserInTeam {
|
||||
return {
|
||||
teamId: ut.teamId,
|
||||
userId: ut.userId,
|
||||
displayName: `${ut.user.firstName} ${ut.user.lastName}`,
|
||||
roleId: ut.role!.id,
|
||||
roleName: ut.role!.name,
|
||||
}
|
||||
}
|
||||
|
||||
protected createFindUserInTeamQuery() {
|
||||
return this.db.getRepository(UserTeam)
|
||||
.createQueryBuilder('ut')
|
||||
.select('ut')
|
||||
.innerJoinAndSelect('ut.user', 'user')
|
||||
.innerJoinAndSelect('ut.role', 'role')
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user