forked from labring/laf
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(system-server): refactor system server apis, support appid;
- Loading branch information
Showing
4 changed files
with
502 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }) | ||
} |
Oops, something went wrong.