Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server): add metered billing module, remove subscription module, discard Prisma #1187

Merged
merged 57 commits into from
May 29, 2023
Merged
Changes from 1 commit
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
19acb20
design metering schema
maslow May 9, 2023
c85d99e
Merge remote-tracking branch 'upstream/main' into design-metering
maslow May 10, 2023
f65e737
Merge remote-tracking branch 'upstream/main' into design-metering
maslow May 12, 2023
e87187a
Merge remote-tracking branch 'upstream/main' into design-metering
maslow May 14, 2023
ef434e7
add region bundle schema
maslow May 14, 2023
bc36980
remove prisma in some modules
maslow May 16, 2023
e7f360d
remove prisma in gateway, website, storage
maslow May 16, 2023
d3b6888
remove prisma in dependency module
maslow May 16, 2023
45c0dd3
remove prisma in account module
maslow May 17, 2023
29ee47b
remove prisma in instance module
maslow May 17, 2023
7bc52d4
remove prisma in auth and user module
maslow May 17, 2023
54550c8
remove prisma in function and trigger module
maslow May 17, 2023
5076425
remove prisma totally
maslow May 17, 2023
9f5b31d
Merge remote-tracking branch 'upstream/main' into design-metering
maslow May 17, 2023
398c3a1
add resource option & template api
maslow May 19, 2023
a2dc9db
rename resource template to bundle
maslow May 19, 2023
fec7757
design metering schema
maslow May 9, 2023
1f51402
add region bundle schema
maslow May 14, 2023
393eda0
remove prisma in some modules
maslow May 16, 2023
52d926d
remove prisma in gateway, website, storage
maslow May 16, 2023
1f51c57
remove prisma in dependency module
maslow May 16, 2023
5938807
remove prisma in account module
maslow May 17, 2023
7e3fbf6
remove prisma in instance module
maslow May 17, 2023
e65b211
remove prisma in auth and user module
maslow May 17, 2023
cba015c
remove prisma in function and trigger module
maslow May 17, 2023
2d2a116
remove prisma totally
maslow May 17, 2023
2093afb
add resource option & template api
maslow May 19, 2023
b3a7d95
rename resource template to bundle
maslow May 19, 2023
10e8d6f
Merge remote-tracking branch 'upstream/metering' into design-metering
maslow May 22, 2023
935c7b2
add billing module & price calculation api
maslow May 23, 2023
1f23d43
add response api typings
maslow May 23, 2023
9130c52
Design metering (#1169)
maslow May 23, 2023
cdfeee4
update app bundle api
maslow May 24, 2023
1238998
impl billing task
maslow May 24, 2023
b6bf134
feat(server): add application billings http api
maslow May 24, 2023
e3f5886
impl billing payment task
maslow May 25, 2023
c415158
restart app while updating bundle
maslow May 25, 2023
5e045ff
update user profile api
maslow May 25, 2023
2f1cb16
fix account api response body
maslow May 26, 2023
1f7d298
feat(web): new metering design (#1171)
LeezQ May 26, 2023
cd72e16
add pagination response decoration
maslow May 26, 2023
34b1f35
feat(web): metering web (#1173)
LeezQ May 26, 2023
cd44872
chore: update entity typings
maslow May 26, 2023
172f9cc
fix(web): modal height (#1175)
LeezQ May 26, 2023
6a240c9
add application trial tier limit
maslow May 26, 2023
b51af3d
feat(server): add invite code feature and billings pagination (#1183)
HUAHUAI23 May 29, 2023
aab7d8e
feat(server): process free trial billing
maslow May 29, 2023
5c06979
fix(web): update app state api & rules add api (#1184)
LeezQ May 29, 2023
1033a18
fix(server): update response struct of rules, add response types
maslow May 29, 2023
ef1d85a
Merge remote-tracking branch 'upstream/design-metering' into design-m…
maslow May 29, 2023
7b558ea
refactor(web): api definitions (#1185)
LeezQ May 29, 2023
8440f77
fix(server): fix trial app logic
maslow May 29, 2023
4815947
Merge remote-tracking branch 'upstream/design-metering' into design-m…
maslow May 29, 2023
5234138
Feat definition (#1186)
LeezQ May 29, 2023
6ed2885
merge main into design-metering
maslow May 29, 2023
f89d90c
add metering yaml to laf helm charts
maslow May 29, 2023
4e4f460
Merge branch 'main' into design-metering
maslow May 29, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add billing module & price calculation api
maslow committed May 23, 2023

Verified

This commit was signed with the committer’s verified signature.
darcyclarke Darcy Clarke
commit 935c7b231be4e7b5205ca06e1432b1e140be2622
11 changes: 11 additions & 0 deletions server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion server/package.json
Original file line number Diff line number Diff line change
@@ -46,6 +46,7 @@
"cron-validate": "^1.4.5",
"database-proxy": "^1.0.0-beta.2",
"dayjs": "^1.11.7",
"decimal.js": "^10.4.3",
"dotenv": "^16.0.3",
"fast-json-patch": "^3.1.1",
"lodash": "^4.17.21",
@@ -108,4 +109,4 @@
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
}
2 changes: 2 additions & 0 deletions server/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ import { AccountModule } from './account/account.module'
import { SettingModule } from './setting/setting.module'
import * as path from 'path'
import { AcceptLanguageResolver, I18nModule, QueryResolver } from 'nestjs-i18n'
import { BillingModule } from './billing/billing.module'

@Module({
imports: [
@@ -60,6 +61,7 @@ import { AcceptLanguageResolver, I18nModule, QueryResolver } from 'nestjs-i18n'
'../src/generated/i18n.generated.ts',
),
}),
BillingModule,
],
controllers: [AppController],
providers: [AppService],
71 changes: 71 additions & 0 deletions server/src/billing/billing.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Body, Controller, Get, Logger, Param, Post } from '@nestjs/common'
import { ApiOperation, ApiTags } from '@nestjs/swagger'
import { CreateApplicationDto } from 'src/application/dto/create-application.dto'
import { ResourceService } from './resource.service'
import { ResponseUtil } from 'src/utils/response'
import { ObjectId } from 'mongodb'
import { BillingService } from './billing.service'
import { RegionService } from 'src/region/region.service'

@ApiTags('Billing')
@Controller('billing')
export class BillingController {
private readonly logger = new Logger(BillingController.name)

constructor(
private readonly resource: ResourceService,
private readonly billing: BillingService,
private readonly region: RegionService,
) {}

/**
* Calculate pricing
* @param dto
*/
@ApiOperation({ summary: 'Calculate pricing' })
@Post('price')
async calculatePrice(@Body() dto: CreateApplicationDto) {
// check regionId exists
const region = await this.region.findOneDesensitized(
new ObjectId(dto.regionId),
)
if (!region) {
return ResponseUtil.error(`region ${dto.regionId} not found`)
}

const result = await this.billing.calculatePrice(dto)
return ResponseUtil.ok(result)
}

/**
* Get resource option list
*/
@ApiOperation({ summary: 'Get resource option list' })
@Get('resource-options')
async getResourceOptions() {
const options = await this.resource.findAll()
const grouped = this.resource.groupByType(options)
return ResponseUtil.ok(grouped)
}

/**
* Get resource option list by region id
*/
@ApiOperation({ summary: 'Get resource option list by region id' })
@Get('resource-options/:regionId')
async getResourceOptionsByRegionId(@Param('regionId') regionId: string) {
const data = await this.resource.findAllByRegionId(new ObjectId(regionId))
return ResponseUtil.ok(data)
}

/**
* Get resource template list
* @returns
*/
@ApiOperation({ summary: 'Get resource template list' })
@Get('resource-bundles')
async getResourceBundles() {
const data = await this.resource.findAllBundles()
return ResponseUtil.ok(data)
}
}
11 changes: 11 additions & 0 deletions server/src/billing/billing.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common'
import { BillingService } from './billing.service'
import { ResourceService } from './resource.service'
import { BillingController } from './billing.controller'

@Module({
controllers: [BillingController],
providers: [BillingService, ResourceService],
exports: [BillingService, ResourceService],
})
export class BillingModule {}
68 changes: 68 additions & 0 deletions server/src/billing/billing.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Injectable } from '@nestjs/common'
import { CreateApplicationDto } from 'src/application/dto/create-application.dto'
import { SystemDatabase } from 'src/database/system-database'
import { ResourceService } from './resource.service'
import { ObjectId } from 'mongodb'
import { ResourceType } from './entities/resource'
import { Decimal } from 'decimal.js'
import * as assert from 'assert'

@Injectable()
export class BillingService {
private readonly db = SystemDatabase.db

constructor(private readonly resource: ResourceService) {}

async calculatePrice(dto: CreateApplicationDto) {
// get options by region id
const options = await this.resource.findAllByRegionId(
new ObjectId(dto.regionId),
)

const groupedOptions = this.resource.groupByType(options)
assert(groupedOptions[ResourceType.CPU], 'cpu option not found')
assert(groupedOptions[ResourceType.Memory], 'memory option not found')
assert(
groupedOptions[ResourceType.StorageCapacity],
'storage capacity option not found',
)
assert(
groupedOptions[ResourceType.DatabaseCapacity],
'database capacity option not found',
)

// calculate cpu price
const cpuOption = groupedOptions[ResourceType.CPU]
const cpuPrice = new Decimal(cpuOption.price).mul(dto.cpu)

// calculate memory price
const memoryOption = groupedOptions[ResourceType.Memory]
const memoryPrice = new Decimal(memoryOption.price).mul(dto.memory)

// calculate storage capacity price
const storageOption = groupedOptions[ResourceType.StorageCapacity]
const storagePrice = new Decimal(storageOption.price).mul(
dto.storageCapacity,
)

// calculate database capacity price
const databaseOption = groupedOptions[ResourceType.DatabaseCapacity]
const databasePrice = new Decimal(databaseOption.price).mul(
dto.databaseCapacity,
)

// calculate total price
const totalPrice = cpuPrice
.add(memoryPrice)
.add(storagePrice)
.add(databasePrice)

return {
cpu: cpuPrice.toNumber(),
memory: memoryPrice.toNumber(),
storageCapacity: storagePrice.toNumber(),
databaseCapacity: databasePrice.toNumber(),
total: totalPrice.toNumber(),
}
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Injectable } from '@nestjs/common'
import { SystemDatabase } from 'src/database/system-database'
import { ObjectId } from 'mongodb'
import { ResourceOption, ResourceBundle } from './entities/resource'
import {
ResourceOption,
ResourceBundle,
ResourceType,
} from './entities/resource'

@Injectable()
export class ResourceService {
@@ -22,6 +26,13 @@ export class ResourceService {
return option
}

async findOneByType(type: ResourceType) {
const option = await this.db
.collection<ResourceOption>('ResourceOption')
.findOne({ type: type })
return option
}

async findAllByRegionId(regionId: ObjectId) {
const options = await this.db
.collection<ResourceOption>('ResourceOption')
@@ -39,4 +50,17 @@ export class ResourceService {

return options
}

groupByType(options: ResourceOption[]) {
type GroupedOptions = {
[key in ResourceType]: ResourceOption
}

const groupedOptions = options.reduce((acc, cur) => {
acc[cur.type] = cur
return acc
}, {} as GroupedOptions)

return groupedOptions
}
}
10 changes: 5 additions & 5 deletions server/src/initializer/initializer.service.ts
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ import {
ResourceOption,
ResourceBundle,
ResourceType,
} from 'src/region/entities/resource'
} from 'src/billing/entities/resource'

@Injectable()
export class InitializerService {
@@ -167,7 +167,7 @@ export class InitializerService {
{
regionId: region._id,
type: ResourceType.CPU,
price: 0.072,
price: 0.000072,
specs: [
{ label: '0.2 Core', value: 200 },
{ label: '0.5 Core', value: 500 },
@@ -180,7 +180,7 @@ export class InitializerService {
{
regionId: region._id,
type: ResourceType.Memory,
price: 0.036,
price: 0.000036,
specs: [
{ label: '256 MB', value: 256 },
{ label: '512 MB', value: 512 },
@@ -194,7 +194,7 @@ export class InitializerService {
{
regionId: region._id,
type: ResourceType.DatabaseCapacity,
price: 0.0072,
price: 0.0000072,
specs: [
{ label: '1 GB', value: 1024 },
{ label: '4 GB', value: 4096 },
@@ -208,7 +208,7 @@ export class InitializerService {
{
regionId: region._id,
type: ResourceType.StorageCapacity,
price: 0.002,
price: 0.000002,
specs: [
{ label: '1 GB', value: 1024 },
{ label: '4 GB', value: 4096 },
42 changes: 2 additions & 40 deletions server/src/region/region.controller.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { Controller, Get, Logger, Param } from '@nestjs/common'
import { Controller, Get, Logger } from '@nestjs/common'
import { ApiOperation, ApiTags } from '@nestjs/swagger'
import { ResponseUtil } from '../utils/response'
import { RegionService } from './region.service'
import { ResourceService } from './resource.service'
import { ObjectId } from 'mongodb'

@ApiTags('Public')
@Controller('regions')
export class RegionController {
private readonly logger = new Logger(RegionController.name)
constructor(
private readonly regionService: RegionService,
private readonly resourceService: ResourceService,
) {}
constructor(private readonly regionService: RegionService) {}

/**
* Get region list
@@ -24,37 +19,4 @@ export class RegionController {
const data = await this.regionService.findAllDesensitized()
return ResponseUtil.ok(data)
}

/**
* Get resource option list
*/
@ApiOperation({ summary: 'Get resource option list' })
@Get('resource-options')
async getResourceOptions() {
const data = await this.resourceService.findAll()
return ResponseUtil.ok(data)
}

/**
* Get resource option list by region id
*/
@ApiOperation({ summary: 'Get resource option list by region id' })
@Get('resource-options/:regionId')
async getResourceOptionsByRegionId(@Param('regionId') regionId: string) {
const data = await this.resourceService.findAllByRegionId(
new ObjectId(regionId),
)
return ResponseUtil.ok(data)
}

/**
* Get resource template list
* @returns
*/
@ApiOperation({ summary: 'Get resource template list' })
@Get('resource-bundles')
async getResourceBundles() {
const data = await this.resourceService.findAllBundles()
return ResponseUtil.ok(data)
}
}
3 changes: 1 addition & 2 deletions server/src/region/region.module.ts
Original file line number Diff line number Diff line change
@@ -2,11 +2,10 @@ import { Global, Module } from '@nestjs/common'
import { RegionService } from './region.service'
import { RegionController } from './region.controller'
import { ClusterService } from './cluster/cluster.service'
import { ResourceService } from './resource.service'

@Global()
@Module({
providers: [RegionService, ClusterService, ResourceService],
providers: [RegionService, ClusterService],
controllers: [RegionController],
exports: [RegionService, ClusterService],
})