Skip to content

Commit

Permalink
feat(system-server): add app collaborator crud apis;
Browse files Browse the repository at this point in the history
  • Loading branch information
maslow committed Sep 8, 2021
1 parent 77ee64d commit 2789b0d
Show file tree
Hide file tree
Showing 12 changed files with 307 additions and 38 deletions.
48 changes: 48 additions & 0 deletions packages/system-server/src/api/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,52 @@ export async function getAccountByAppid(uid: string) {
.getOne()

return ret.data
}

/**
* Get an account by username
*/
export async function getAccountByUsername(username: string) {
assert.ok(username, 'empty username got')

const db = DatabaseAgent.sys_db
const ret = await db.collection(Constants.cn.accounts)
.where({ username: username })
.getOne()

return ret.data
}

/**
* Check if given account id is valid
* @param uid
* @returns
*/
export async function isValidAccountId(uid: string) {
if (!uid) return false
const account = await getAccountByAppid(uid)
return account ? true : false
}

/**
* Check if given role names are valid
* @param role_names
*/
export function isValidRoleNames(role_names: string[]): boolean {
if (!(role_names instanceof Array))
return false

const roles = getRoles()
for (const rn of role_names)
if (!roles[rn]) return false

return true
}

/**
* Get roles
* @returns
*/
export function getRoles() {
return Constants.roles
}
23 changes: 22 additions & 1 deletion packages/system-server/src/api/application.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* @Author: Maslow<wangfugen@126.com>
* @Date: 2021-08-28 22:00:45
* @LastEditTime: 2021-09-06 22:04:37
* @LastEditTime: 2021-09-08 18:39:01
* @Description: Application APIs
*/

Expand Down Expand Up @@ -116,6 +116,27 @@ export async function getApplicationDbAccessor(app: ApplicationStruct) {
return accessor
}


/**
* Get user's roles of an application
* @param uid
* @param app
* @returns
*/
export function getUserRolesOfApplication(uid: string, app: ApplicationStruct) {
if (app.created_by === uid) {
return [Constants.roles.owner.name]
}

// reject if not the collaborator
const [found] = app.collaborators.filter(co => co.uid === uid)
if (!found) {
return []
}

return found.roles
}

/**
* Get the db uri of an application
* @param app
Expand Down
7 changes: 7 additions & 0 deletions packages/system-server/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ export default class Config {
return process.env['LOG_LEVEL'] ?? (this.isProd ? 'info' : 'debug')
}

/**
* the logger level : 'fatal', 'error', 'warning', 'info', 'debug', 'trace'
*/
static get DB_LOG_LEVEL(): string {
return process.env['DB_LOG_LEVEL'] ?? (this.isProd ? 'warning' : 'debug')
}

/**
* the serving port, default is 9000
*/
Expand Down
6 changes: 3 additions & 3 deletions packages/system-server/src/constants/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ const owner = [
export const roles = {
developer: {
name: 'developer',
label: 'Application Developer',
label: 'Developer',
permissions: developer
},
dba: {
name: 'dba',
label: 'Application Database Administrator',
label: 'Database Administrator',
permissions: dba
},
operator: {
Expand All @@ -49,7 +49,7 @@ export const roles = {
},
owner: {
name: 'owner',
label: 'Application Owner',
label: 'Owner',
permissions: owner
}
}
4 changes: 2 additions & 2 deletions packages/system-server/src/lib/db-agent.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* @Author: Maslow<wangfugen@126.com>
* @Date: 2021-07-30 10:30:29
* @LastEditTime: 2021-09-03 15:28:45
* @LastEditTime: 2021-09-08 23:07:02
* @Description:
*/

Expand Down Expand Up @@ -69,7 +69,7 @@ export class DatabaseAgent {
const { database } = this.parseConnectionUri(uri)
const accessor = new MongoAccessor(database, uri)

accessor.setLogger(createLogger(`accessor:${database}`, 'warning'))
accessor.setLogger(createLogger(`accessor:${database}`, Config.DB_LOG_LEVEL))
accessor.init()
.then(() => {
logger.info(`db:${database} connected`)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* @Author: Maslow<wangfugen@126.com>
* @Date: 2021-07-30 10:30:29
* @LastEditTime: 2021-08-31 15:35:22
* @LastEditTime: 2021-09-08 03:33:08
* @Description:
*/

Expand Down
5 changes: 2 additions & 3 deletions packages/system-server/src/router/account/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/*
* @Author: Maslow<wangfugen@126.com>
* @Date: 2021-07-30 10:30:29
* @LastEditTime: 2021-08-29 11:33:26
* @LastEditTime: 2021-09-08 03:33:19
* @Description:
*/

import { Router } from 'express'
import { handleEdit } from './edit'
import { handleProfile } from './profile'
import { handleProfile } from './get'
import { handleSignIn } from './signin'
import { handleSignUp } from './signup'

Expand All @@ -18,7 +18,6 @@ export const AccountRouter = Router()
*/
AccountRouter.post('/login', handleSignIn)


/**
* get account profile
*/
Expand Down
9 changes: 7 additions & 2 deletions packages/system-server/src/router/account/signup.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* @Author: Maslow<wangfugen@126.com>
* @Date: 2021-07-30 10:30:29
* @LastEditTime: 2021-09-06 22:19:49
* @LastEditTime: 2021-09-08 20:26:21
* @Description:
*/

Expand All @@ -19,11 +19,15 @@ export async function handleSignUp(req: Request, res: Response) {

const db = DatabaseAgent.sys_db

const { username, password } = req.body
const { username, password, name } = req.body
if (!username || !password) {
return res.send({ error: 'username or password cannot be empty' })
}

if (!name) {
return res.send({ error: 'name cannot be empty' })
}

// check if account exists
const { total } = await db.collection(Constants.cn.accounts).where({ username }).count()
if (total > 0) {
Expand All @@ -37,6 +41,7 @@ export async function handleSignUp(req: Request, res: Response) {
quota: {
app_count: Config.ACCOUNT_DEFAULT_APP_QUOTA
},
name: name,
password: hashPassword(password),
created_at: Date.now(),
updated_at: Date.now()
Expand Down
183 changes: 183 additions & 0 deletions packages/system-server/src/router/application/collaborator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
* @Author: Maslow<wangfugen@126.com>
* @Date: 2021-08-31 15:00:04
* @LastEditTime: 2021-09-08 23:05:03
* @Description:
*/

import { Request, Response } from 'express'
import { getApplicationByAppid } from '../../api/application'
import { checkPermission } from '../../api/permission'
import { Constants } from '../../constants'
import { DatabaseAgent } from '../../lib/db-agent'
import { permissions } from '../../constants/permissions'
import { getAccountByUsername, getRoles, isValidAccountId, isValidRoleNames } from '../../api/account'
import { array2map, mergeMap2ArrayByKey } from '../../utils/array'

const { APPLICATION_UPDATE } = permissions

/**
* The handler of getting collaborators of an application
*/
export async function handleGetCollaborators(req: Request, res: Response) {
const uid = req['auth']?.uid
if (!uid)
return res.status(401).send()


const appid = req.params.appid
const app = await getApplicationByAppid(appid)
if (!app)
return res.status(422).send('invalid appid')

if (!app.collaborators?.length) {
return res.send({ data: [] })
}

const db = DatabaseAgent.sys_db
const co_ids = app.collaborators.map(co => co.uid)
const r = await db.collection(Constants.cn.accounts)
.where({ _id: db.command.in(co_ids) })
.field(['_id', 'username', 'name'])
.get()

// merge app
const user_map = array2map(r.data, '_id', true)
const ret = mergeMap2ArrayByKey(user_map, app.collaborators, 'uid', 'user')
return res.send({
data: ret
})
}

/**
* The handler of inviting collaborator
*/
export async function handleInviteCollaborator(req: Request, res: Response) {
const uid = req['auth']?.uid
const db = DatabaseAgent.sys_db
const { member_id, roles } = req.body

if (!isValidRoleNames(roles))
return res.status(422).send('invalid roles')

if (!isValidAccountId(member_id))
return res.status(422).send('invalid member_id')

const appid = req.params.appid
const app = await getApplicationByAppid(appid)
if (!app) {
return res.status(422).send('app not found')
}

// check permission
const code = await checkPermission(uid, APPLICATION_UPDATE.name, app)
if (code) {
return res.status(code).send()
}

// reject if collaborator exists
const exists = app.collaborators.filter(it => it.uid === member_id)
if (exists.length) {
return res.status(422).send('collaborator already exists')
}

// reject if the application owner get here
if (app.created_by === member_id) {
return res.status(422).send('collaborator is already the owner of this application')
}

// add a collaborator
const collaborator = {
uid: member_id,
roles,
created_at: Date.now()
}
const ret = await db.collection(Constants.cn.applications)
.where({ appid: app.appid })
.update({ collaborators: db.command.addToSet(collaborator) })

return res.send({
data: ret
})
}

/**
* The handler of searching collaborator
*/
export async function handleSearchCollaborator(req: Request, res: Response) {
const uid = req['auth']?.uid
if (!uid)
return res.status(401).send()

const username = req.body?.username
if (!username) return res.status(422).send('username cannot be empty')

const account = await getAccountByUsername(username)
if (!account) {
return res.send({ data: null })
}

return res.send({
data: {
_id: account._id,
username: account.username,
name: account.name
}
})
}

/**
* The handler of getting roles
*/
export async function handleGetRoles(req: Request, res: Response) {
const uid = req['auth']?.uid
if (!uid)
return res.status(401).send()

const roles = getRoles()
const rets = Object.keys(roles)
.filter(key => key !== roles.owner.name)
.map(key => roles[key])

return res.send({
data: rets
})
}

/**
* The handler of removing collaborator of an application
*/
export async function handleRemoveCollaborator(req: Request, res: Response) {
const uid = req['auth']?.uid
if (!uid)
return res.status(401).send()

const appid = req.params.appid
const app = await getApplicationByAppid(appid)
if (!app)
return res.status(422).send('invalid appid')

// check permission
const code = await checkPermission(uid, APPLICATION_UPDATE.name, app)
if (code) {
return res.status(code).send()
}

// check collaborator_id
const collaborator_id = req.params.collaborator_id
const [found] = app.collaborators.filter(co => co.uid === collaborator_id)
if (!found) {
return res.status(422).send('invalid collaborator_id')
}

const db = DatabaseAgent.sys_db
const r = await db.collection(Constants.cn.applications)
.where({ appid })
.update({
collaborators: db.command.pull({ uid: collaborator_id })
})

return res.send({
data: r
})
}
Loading

0 comments on commit 2789b0d

Please sign in to comment.