Skip to content

Commit

Permalink
feat(system-server): refactor system server apis, support appid;
Browse files Browse the repository at this point in the history
  • Loading branch information
maslow committed Aug 30, 2021
1 parent 508c76e commit 446740f
Show file tree
Hide file tree
Showing 4 changed files with 502 additions and 0 deletions.
189 changes: 189 additions & 0 deletions packages/system-server/src/api/function.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/*
* @Author: Maslow<wangfugen@126.com>
* @Date: 2021-07-30 10:30:29
* @LastEditTime: 2021-08-29 18:23:38
* @Description:
*/

import { Constants } from "../constants"
import { DatabaseAgent } from "../lib/db-agent"
import { compileTs2js } from 'cloud-function-engine/dist/utils'
import { CloudFunctionStruct } from "cloud-function-engine"
import { ClientSession, ObjectId } from 'mongodb'
import * as assert from 'assert'
import { logger } from "../lib/logger"
import { ApplicationStruct, getApplicationDbAccessor } from "./application"

const db = DatabaseAgent.sys_db

/**
* Load function data by its name
* @param func_name
* @returns
*/
export async function getFunctionByName(appid: string, func_name: string) {
const r = await db.collection(Constants.cn.functions)
.where({ name: func_name, appid })
.getOne<CloudFunctionStruct>()

assert.ok(r.ok, `getCloudFunction() failed to get function [${func_name}]: ${r.error.toString()}`)
return r.data
}

/**
* Load function data by its id
* @param func_name
* @returns
*/
export async function getFunctionById(appid: string, func_id: string) {
const r = await db.collection(Constants.cn.functions)
.where({ _id: func_id, appid })
.getOne()

assert.ok(r.ok, `getCloudFunctionById() failed to get function [${func_id}]: ${r.error.toString()}`)
return r.data
}



/**
* Publish functions
* Means that copying sys db functions to app db
*/
export async function publishFunctions(app: ApplicationStruct) {

// read functions from sys db
const ret = await DatabaseAgent.sys_accessor.db
.collection(Constants.cn.functions)
.find({
appid: app._id
})
.toArray()

// compile functions
const data = ret.map(fn => compileFunction(fn))

// write functions to app db
const app_accessor = await getApplicationDbAccessor(app)
const session = app_accessor.conn.startSession()

try {
await session.withTransaction(async () => {
const _db = app_accessor.db
const app_coll = _db.collection(Constants.function_collection)
await app_coll.deleteMany({}, { session })
await app_coll.insertMany(data, { session })
})
} catch (error) {
logger.error(error)
} finally {
await session.endSession()
}
}

/**
* Compile function codes (from typescript to javascript)
* @param func
*/
function compileFunction(func: any) {
func.compiledCode = compileTs2js(func.code)
return func
}

/**
* Deploy functions which pushed from remote environment
*/
export async function deployFunctions(functions: CloudFunctionStruct[]) {
assert.ok(functions)
assert.ok(functions instanceof Array)

const accessor = DatabaseAgent.sys_accessor

const data = functions
const session = accessor.conn.startSession()

try {
await session.withTransaction(async () => {
for (const func of data) {
await _deployOneFunction(func, session)
}
})
} finally {
await session.endSession()
}
}

/**
* Deploy a function, use in `deployFunctions()`
* @param func the cloud function to be deployed
* @param session mongodb session for transaction operations
* @private
* @see deployFunctions()
* @returns
*/
async function _deployOneFunction(func: CloudFunctionStruct, session: ClientSession) {

await _processFunctionWithSameNameButNotId(func, session)

const db = DatabaseAgent.sys_accessor.db
const r = await db.collection(Constants.cn.functions).findOne({ _id: new ObjectId(func._id) }, { session })

const data = {
...func
}

// if exists function
if (r) {
delete data['_id']
const ret = await db.collection(Constants.cn.functions).updateOne({ _id: r._id }, {
$set: data
}, { session })

assert(ret.matchedCount, `deploy: update function ${func.name} occurred error`)
return
}

// if new function
data._id = new ObjectId(data._id) as any

const ret = await db.collection(Constants.cn.functions).insertOne(data as any, { session })
assert(ret.insertedId, `deploy: add function ${func.name} occurred error`)
}

/**
* Remove functions which have same name but different _id.
* @param func the function to be processing
* @param session the mongodb session for transaction operations
*/
async function _processFunctionWithSameNameButNotId(func: CloudFunctionStruct, session: ClientSession) {
const db = DatabaseAgent.sys_accessor.db

// remove functions
const r = await db.collection(Constants.cn.functions).findOneAndDelete(
{
_id: {
$ne: new ObjectId(func._id)
},
name: func.name
},
{ session })

if (!r.value) {
return
}

logger.warn(`delete local func ${r?.value?._id} with same name with (${func._id}:${func.name}) but different id `)

// remove its triggers if any
const r0 = await db.collection(Constants.cn.triggers).deleteMany(
{
func_id: r?.value?._id?.toString()
},
{ session })

if (!r0.deletedCount) {
return
}

logger.warn(`delete triggers of func ${func._id} which been deleted`, r0)
}
54 changes: 54 additions & 0 deletions packages/system-server/src/api/permission.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* @Author: Maslow<wangfugen@126.com>
* @Date: 2021-07-30 10:30:29
* @LastEditTime: 2021-08-30 15:48:51
* @Description:
*/

import * as assert from 'assert'
import { Constants } from '../constants'
import { ApplicationStruct } from './application'

/**
* Check if a user have permission for application
* @param uid the account id
* @param permission the permission name
* @param app the application which checked for
* @returns 0 means ok, 401 means unauthorized, 403 means permission denied
*/
export async function checkPermission(uid: string, permission: string, app: ApplicationStruct): Promise<number> {
if (!uid) return 401
assert.ok(permission, 'empty permission got')
assert.ok(app, 'empty app got')

// pass directly while the app owner here
if (uid === app.created_by) return 0

// reject while uid is not the collaborator
const [collaborator] = app.collaborators?.filter(co => co.uid === uid) ?? []
if (!collaborator) return 403

// reject while uid not have the permission
const roles = collaborator.roles
const permissions = getPermissionsOfRoles(roles)
if (!permissions.includes(permission)) {
return 403
}

return 0
}

/**
* Get permission names by a list of role names
* @param roles_names The role names
* @returns
*/
export function getPermissionsOfRoles(roles_names: string[]) {
const permissions = []
for (const name of roles_names) {
const pns = Constants.roles[name]?.permissions ?? []
permissions.push(...pns)
}

return permissions.map(pn => pn.name)
}
120 changes: 120 additions & 0 deletions packages/system-server/src/api/policy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* @Author: Maslow<wangfugen@126.com>
* @Date: 2021-07-30 10:30:29
* @LastEditTime: 2021-08-29 18:24:59
* @Description:
*/

import * as assert from 'assert'
import { Constants } from '../constants'
import { DatabaseAgent } from "../lib/db-agent"
import { ClientSession, ObjectId } from 'mongodb'
import { ApplicationStruct, getApplicationDbAccessor } from './application'


/**
* Publish access policies
* Means that copying sys db functions to app db
*/
export async function publishAccessPolicy(app: ApplicationStruct) {
// read policies from sys db
const ret = await DatabaseAgent.sys_accessor.db
.collection(Constants.cn.policies)
.find({
appid: app._id
})
.toArray()

// write policies to app db
const app_accessor = await getApplicationDbAccessor(app)
const session = app_accessor.conn.startSession()

try {
await session.withTransaction(async () => {
const _db = app_accessor.db
const app_coll = _db.collection(Constants.policy_collection)
await app_coll.deleteMany({}, { session })
await app_coll.insertMany(ret, { session })
})
} finally {
await session.endSession()
}
}


/**
* Deploy policies which pushed from remote environment
*/
export async function deployPolicies(policies: any[]) {
assert.ok(policies)
assert.ok(policies instanceof Array)

const accessor = DatabaseAgent.sys_accessor

const data = policies
const session = accessor.conn.startSession()

try {
await session.withTransaction(async () => {
for (const item of data) {
await _deployOnePolicy(item, session)
}
})
} finally {
await session.endSession()
}
}

/**
* Deploy a policy used by `deployPolicies`.
* @param policy the policy data to be deployed
* @param session the mongodb session for transaction operations
* @see deployPolicies
* @private
* @returns
*/
async function _deployOnePolicy(policy: any, session: ClientSession) {

await _deletePolicyWithSameNameButNotId(policy, session)

const db = DatabaseAgent.sys_accessor.db
const r = await db.collection(Constants.cn.policies).findOne({ _id: new ObjectId(policy._id) }, { session })

const data = {
...policy
}


// if exists
if (r) {
delete data['_id']
const ret = await db.collection(Constants.cn.policies).updateOne({ _id: r._id }, {
$set: data
}, { session })

assert(ret.matchedCount, `deploy: update policy ${policy.name} occurred error`)
return
}

// if new
data._id = new ObjectId(data._id) as any
const ret = await db.collection(Constants.cn.policies).insertOne(data as any, { session })
assert(ret.insertedId, `deploy: add policy ${policy.name} occurred error`)
}

/**
* Remove policy which have same name but different _id.
* @param policy the policy to be processing
* @param session the mongodb session for transaction operations
* @see _deployOnePolicy()
* @private
*/
async function _deletePolicyWithSameNameButNotId(policy: any, session: ClientSession) {
const db = DatabaseAgent.sys_accessor.db
await db.collection(Constants.cn.policies).findOneAndDelete({
_id: {
$ne: new ObjectId(policy._id)
},
name: policy.name
}, { session })
}
Loading

0 comments on commit 446740f

Please sign in to comment.