Skip to content

Commit

Permalink
feat(server): add account transaction (#1014)
Browse files Browse the repository at this point in the history
  • Loading branch information
maslow authored Apr 7, 2023
1 parent f6c39ee commit c7e6d15
Show file tree
Hide file tree
Showing 11 changed files with 176 additions and 299 deletions.
72 changes: 70 additions & 2 deletions server/prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -1,10 +1,36 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// These models cover multiple aspects, such as subscriptions, accounts, applications,
// storage, databases, cloud functions, gateways, and SMS verification codes.
// Here's a brief description:
//
// 1. Subscription models (Subscription and SubscriptionRenewal): Represent the state
// and plans of subscriptions and their renewals.
// 2. Account models (Account and AccountChargeOrder): Track account balances and
// recharge records.
// 3. Application models (Application and ApplicationConfiguration): Represent
// application configurations and states.
// 4. Storage models (StorageUser and StorageBucket): Represent the state and policies
// of storage users and buckets.
// 5. Database models (Database, DatabasePolicy, and DatabasePolicyRule): Represent the
// state, policies, and rules of databases.
// 6. Cloud Function models (CloudFunction and CronTrigger): Represent the configuration
// and state of cloud functions and scheduled triggers.
// 7. Gateway models (RuntimeDomain, BucketDomain, and WebsiteHosting): Represent the
// state and configuration of runtime domains, bucket domains, and website hosting.
// 8. Authentication provider models (AuthProvider): Represent the configuration and state
// of authentication providers.
// 9. SMS verification code models (SmsVerifyCode): Represent the type, state, and
// related information of SMS verification codes.
//
// These models together form a complete cloud service system, covering subscription
// management, account management, application deployment, storage management, database
// management, cloud function deployment and execution, gateway configuration, and SMS
// verification, among other functionalities.

generator client {
provider = "prisma-client-js"
// previewFeatures = ["interactiveTransactions"]
// binaryTargets = ["native", "linux-arm64-openssl-1.1.x"]
binaryTargets = ["native"]
}

Expand Down Expand Up @@ -212,6 +238,17 @@ model Runtime {

// subscriptions schemas

// Subscription section mainly consists of two models: Subscription and SubscriptionRenewal.
//
// 1. Subscription: Represents the state, phase, and renewal plan of a subscription. It includes
// the created, updated, and deleted states (SubscriptionState enum); the pending, valid, expired,
// expired and stopped, and deleted phases (SubscriptionPhase enum); and manual, monthly, or
// yearly renewal plans (SubscriptionRenewalPlan enum). This model also contains the associated
// application (Application).
//
// 2. SubscriptionRenewal: Represents the state, duration, and amount of a subscription renewal.
// It includes the pending, paid, and failed renewal phases (SubscriptionRenewalPhase enum).

enum SubscriptionState {
Created
Deleted
Expand Down Expand Up @@ -274,6 +311,27 @@ model SubscriptionRenewal {
createdBy String @db.ObjectId
}

// desired state of resource
enum SubscriptionUpgradePhase {
Pending
Completed
Failed
}

model SubscriptionUpgrade {
id String @id @default(auto()) @map("_id") @db.ObjectId
appid String
subscriptionId String
originalBundleId String @db.ObjectId
targetBundleId String @db.ObjectId
phase SubscriptionUpgradePhase @default(Pending)
restart Boolean @default(false)
message String?
lockedAt DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

// accounts schemas

model Account {
Expand All @@ -285,6 +343,16 @@ model Account {
createdBy String @unique @db.ObjectId
}

model AccountTransaction {
id String @id @default(auto()) @map("_id") @db.ObjectId
accountId String @db.ObjectId
amount Int
balance Int
message String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

enum AccountChargePhase {
Pending
Paid
Expand Down
18 changes: 17 additions & 1 deletion server/src/account/account.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
Body,
Controller,
Get,
HttpCode,
Logger,
Param,
Post,
Expand All @@ -22,6 +21,7 @@ import { PaymentChannelService } from './payment/payment-channel.service'
import { WeChatPayOrderResponse, WeChatPayTradeState } from './payment/types'
import { WeChatPayService } from './payment/wechat-pay.service'
import { Response } from 'express'
import * as assert from 'assert'

@ApiTags('Account')
@Controller('accounts')
Expand Down Expand Up @@ -160,12 +160,28 @@ export class AccountController {
return
}

// get account
const account = await tx.account.findFirst({
where: { id: order.accountId },
})
assert(account, `account not found ${order.accountId}`)

// update account balance
await tx.account.update({
where: { id: order.accountId },
data: { balance: { increment: order.amount } },
})

// create account transaction
await tx.accountTransaction.create({
data: {
accountId: order.accountId,
amount: order.amount,
balance: order.amount + account.balance,
message: 'account charge',
},
})

this.logger.log(`wechatpay order success: ${tradeOrderId}`)
})
} catch (err) {
Expand Down
53 changes: 1 addition & 52 deletions server/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
import {
Body,
Controller,
Get,
Post,
Query,
Req,
Res,
UseGuards,
} from '@nestjs/common'
import { Body, Controller, Get, Post, Req, UseGuards } from '@nestjs/common'
import {
ApiBearerAuth,
ApiOperation,
ApiResponse,
ApiTags,
} from '@nestjs/swagger'
import { Response } from 'express'
import { ApiResponseUtil, ResponseUtil } from '../utils/response'
import { IRequest } from '../utils/interface'
import { UserDto } from '../user/dto/user.response'
Expand All @@ -27,47 +17,6 @@ import { Pat2TokenDto } from './dto/pat2token.dto'
export class AuthController {
constructor(private readonly authService: AuthService) {}

/**
* Redirect to login page
*/
@ApiResponse({ status: 302 })
@ApiOperation({ summary: 'Redirect to login page' })
@Get('login')
async getSigninUrl(@Res() res: Response): Promise<any> {
const url = this.authService.getSignInUrl()
return res.redirect(url)
}

/**
* Redirect to register page
* @param res
* @returns
*/
@ApiResponse({ status: 302 })
@ApiOperation({ summary: 'Redirect to register page' })
@Get('register')
async getSignupUrl(@Res() res: Response) {
const url = this.authService.getSignUpUrl()
return res.redirect(url)
}

/**
* Get user token by auth code
* @param code
* @returns
*/
@ApiOperation({ summary: 'Get user token by auth code' })
@ApiResponse({ type: ResponseUtil })
@Get('code2token')
async code2token(@Query('code') code: string) {
const token = await this.authService.code2token(code)
if (!token) {
return ResponseUtil.error('invalid code')
}

return ResponseUtil.ok(token)
}

/**
* Get user token by PAT
* @param pat
Expand Down
2 changes: 0 additions & 2 deletions server/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { PassportModule } from '@nestjs/passport'
import { ServerConfig } from '../constants'
import { UserModule } from '../user/user.module'
import { AuthService } from './auth.service'
import { CasdoorService } from './casdoor.service'
import { JwtStrategy } from './jwt.strategy'
import { AuthController } from './auth.controller'
import { HttpModule } from '@nestjs/axios'
Expand All @@ -31,7 +30,6 @@ import { AuthenticationService } from './authentication.service'
providers: [
AuthService,
JwtStrategy,
CasdoorService,
PatService,
UserPasswordService,
PhoneService,
Expand Down
80 changes: 1 addition & 79 deletions server/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { Injectable, Logger } from '@nestjs/common'
import { JwtService } from '@nestjs/jwt'
import { CasdoorService } from './casdoor.service'
import { User } from '@prisma/client'
import { UserService } from '../user/user.service'
import { ServerConfig } from '../constants'
import * as assert from 'node:assert'
import { PatService } from 'src/user/pat.service'

Expand All @@ -12,66 +9,9 @@ export class AuthService {
logger: Logger = new Logger(AuthService.name)
constructor(
private readonly jwtService: JwtService,
private readonly casdoorService: CasdoorService,
private readonly userService: UserService,
private readonly patService: PatService,
) {}

/**
* Get user token by casdoor code:
* - code is casdoor code
* - user token is laf server token, NOT casdoor token
* @param code
* @returns
*/
async code2token(code: string): Promise<string> {
try {
const casdoorUser = await this.casdoorService.code2user(code)
if (!casdoorUser) return null

// Get or create laf user
assert(casdoorUser.id, 'casdoor user id is empty')
const profile = await this.userService.getProfileByOpenid(casdoorUser.id)
let user = profile?.user
if (!user) {
user = await this.userService.create({
username: casdoorUser.username,
email: casdoorUser.email,
phone: casdoorUser.phone,
profile: {
create: {
openid: casdoorUser.id,
name: casdoorUser.displayName,
avatar: casdoorUser.avatar,
from: ServerConfig.CASDOOR_CLIENT_ID,
},
},
})
} else {
// update it
user = await this.userService.updateUser({
where: { id: user.id },
data: {
username: casdoorUser.username,
email: casdoorUser.email,
phone: casdoorUser.phone,
profile: {
update: {
name: casdoorUser.displayName,
avatar: casdoorUser.avatar,
},
},
},
})
}

return this.getAccessTokenByUser(user)
} catch (error) {
this.logger.error(error)
return null
}
}

/**
* Get token by PAT
* @param user
Expand All @@ -94,26 +34,8 @@ export class AuthService {
* @returns
*/
getAccessTokenByUser(user: User): string {
const payload = {
sub: user.id,
}
const payload = { sub: user.id }
const token = this.jwtService.sign(payload)
return token
}

/**
* Generate login url
* @returns
*/
getSignInUrl(): string {
return this.casdoorService.getSignInUrl()
}

/**
* Generate register url
* @returns
*/
getSignUpUrl(): string {
return this.casdoorService.getSignUpUrl()
}
}
Loading

0 comments on commit c7e6d15

Please sign in to comment.