feat: serverless access db support

This commit is contained in:
Innei
2022-04-24 22:36:53 +08:00
parent a4415c6e3d
commit 2dbca3252d
6 changed files with 199 additions and 52 deletions

View File

@@ -0,0 +1,32 @@
import {
Severity,
index,
modelOptions,
mongoose,
prop,
} from '@typegoose/typegoose'
export const ServerlessStorageCollectionName = `serverlessstorages`
@modelOptions({
schemaOptions: {},
options: {
customName: ServerlessStorageCollectionName,
allowMixed: Severity.ALLOW,
},
})
@index({ namespace: 1, key: 1 })
export class ServerlessStorageModel {
@prop({ index: 1, required: true })
namespace: string
@prop({ required: true })
key: string
@prop({ type: mongoose.Schema.Types.Mixed, required: true })
value: any
get uniqueKey(): string {
return `${this.namespace}/${this.key}`
}
}

View File

@@ -134,7 +134,7 @@ And other global api is all banned.
- [ ] HTTP Methods: POST, PUT, DELETE, PATCH
- [x] ResponseType: buffer, stream
- [ ] handle safeEval throw
- [ ] MongoDb inject (can access db)
- [x] MongoDb inject (can access db)
- [x] set Content-Type
- [x] ESM AST Parser (ImportStatement)
- [x] Cron to clean require cache

View File

@@ -31,6 +31,7 @@ import {
FunctionContextRequest,
FunctionContextResponse,
} from './function.types'
import { ServerlessStorageCollectionName } from './serverless.model'
@Injectable()
export class ServerlessService {
@@ -70,6 +71,161 @@ export class ServerlessService {
public get model() {
return this.snippetModel
}
private mockStorageCache() {
return {
get: async (key: string) => {
const client = this.cacheService.getClient()
return await client.hget(getRedisKey(RedisKeys.ServerlessStorage), key)
},
set: async (key: string, value: object | string) => {
const client = this.cacheService.getClient()
return await client.hset(
getRedisKey(RedisKeys.ServerlessStorage),
key,
typeof value === 'string' ? value : JSON.stringify(value),
)
},
del: async (key: string) => {
const client = this.cacheService.getClient()
return await client.hdel(getRedisKey(RedisKeys.ServerlessStorage), key)
},
} as const
}
async mockGetMaster() {
const collection = this.databaseService.db.collection('users')
const cur = collection.aggregate([
{
$project: {
id: 1,
_id: 1,
username: 1,
name: 1,
introduce: 1,
avatar: 1,
mail: 1,
url: 1,
lastLoginTime: 1,
lastLoginIp: 1,
socialIds: 1,
},
},
])
return await cur.next().then((doc) => {
cur.close()
return doc
})
}
mockDb(namespace: string) {
const db = this.databaseService.db
const collection = db.collection(ServerlessStorageCollectionName)
const checkRecordIsExist = async (key: string) => {
const has = await collection
.countDocuments({
namespace,
key,
})
.then((count) => count > 0)
return has
}
const updateKey = async (key: string, value: any) => {
if (!(await checkRecordIsExist(key))) {
throw new InternalServerErrorException('key not exist')
}
return collection.updateOne(
{
namespace,
key,
},
{
$set: {
value,
},
},
)
}
return {
async get(key: string) {
return collection
.findOne({
namespace,
key,
})
.then((doc) => {
return doc?.value ?? null
})
},
async find(condition: KV) {
if (typeof condition !== 'object') {
throw new InternalServerErrorException('condition must be object')
}
condition.namespace = namespace
return collection
.aggregate([
{ $match: condition },
{
$project: {
value: 1,
key: 1,
_id: 1,
},
},
])
.toArray()
},
async set(key: string, value: any) {
if (typeof key !== 'string') {
throw new InternalServerErrorException('key must be string')
}
if (await checkRecordIsExist(key)) {
return updateKey(key, value)
}
return collection.insertOne({
namespace,
key,
value,
})
},
async insert(key: string, value: any) {
const has = await collection
.countDocuments({
namespace,
key,
})
.then((count) => count > 0)
if (has) {
throw new InternalServerErrorException('key already exists')
}
return collection.insertOne({
namespace,
key,
value,
})
},
update: updateKey,
del(key: string) {
return collection.deleteOne({
namespace,
key,
})
},
} as const
}
async injectContextIntoServerlessFunctionAndCall(
model: SnippetModel,
context: { req: FunctionContextRequest; res: FunctionContextResponse },
@@ -88,62 +244,17 @@ export class ServerlessService {
params: Object.assign({}, context.req.params),
storage: {
cache: {
get: async (key: string) => {
const client = this.cacheService.getClient()
return await client.hget(
getRedisKey(RedisKeys.ServerlessStorage),
key,
)
},
set: async (key: string, value: object | string) => {
const client = this.cacheService.getClient()
return await client.hset(
getRedisKey(RedisKeys.ServerlessStorage),
key,
typeof value === 'string' ? value : JSON.stringify(value),
)
},
del: async (key: string) => {
const client = this.cacheService.getClient()
return await client.hdel(
getRedisKey(RedisKeys.ServerlessStorage),
key,
)
},
},
db: {},
cache: this.mockStorageCache(),
db: this.mockDb(
`${model.reference || '#########debug######'}@${model.name}`,
),
},
model,
document,
name: model.name,
reference: model.reference,
getMaster: async () => {
const collection = this.databaseService.db.collection('users')
const cur = collection.aggregate([
{
$project: {
id: 1,
_id: 1,
username: 1,
name: 1,
introduce: 1,
avatar: 1,
mail: 1,
url: 1,
lastLoginTime: 1,
lastLoginIp: 1,
socialIds: 1,
},
},
])
return await cur.next().then((doc) => {
cur.close()
return doc
})
},
getMaster: this.mockGetMaster.bind(this),
writeAsset: async (
path: string,