Skip to content

Commit

Permalink
feat(server): implement collection creation api. (#431)
Browse files Browse the repository at this point in the history
Signed-off-by: maslow <wangfugen@126.com>
  • Loading branch information
maslow authored Nov 25, 2022
1 parent ad79951 commit 6d98a4f
Show file tree
Hide file tree
Showing 14 changed files with 11,638 additions and 9,172 deletions.
2 changes: 1 addition & 1 deletion core/controllers/application/resourcer/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func CreateDatabase(ctx context.Context, c client.Client, schema *runtime.Scheme
// Create a new database
var db v1.Database
db.Namespace = app.Namespace
db.Name = "mongodb"
db.Name = app.Spec.AppId
db.Labels = map[string]string{
"laf.dev/appid": app.Spec.AppId,
}
Expand Down
20,528 changes: 11,383 additions & 9,145 deletions server/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"casdoor-nodejs-sdk": "^1.3.0",
"dotenv": "^16.0.3",
"fast-json-patch": "^3.1.1",
"mongodb": "^4.12.1",
"nanoid": "^3.3.4",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
Expand Down
2 changes: 1 addition & 1 deletion server/src/applications/applications.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
HttpException,
HttpStatus,
} from '@nestjs/common'
import { ApiBody, ApiExtraModels, ApiResponse, ApiTags } from '@nestjs/swagger'
import { ApiTags } from '@nestjs/swagger'
import { IRequest } from 'src/common/types'
import { JwtAuthGuard } from '../auth/jwt-auth.guard'
import { ApiResponseUtil, ResponseUtil } from '../common/response'
Expand Down
1 change: 1 addition & 0 deletions server/src/applications/applications.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ import { ApplicationsController } from './applications.controller'
@Module({
controllers: [ApplicationsController],
providers: [ApplicationsService],
exports: [ApplicationsService],
})
export class ApplicationsModule {}
1 change: 0 additions & 1 deletion server/src/applications/applications.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export class ApplicationsService {
// create app resources
const namespace = GetApplicationNamespaceById(appid)
const app = new Application(dto.name, namespace)
app.setAppid(appid)
app.setUserId(userid)

app.spec = new ApplicationSpec({
Expand Down
11 changes: 0 additions & 11 deletions server/src/applications/entities/application.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,6 @@ export class Application implements KubernetesObject {
this.spec = new ApplicationSpec()
}

setAppid(appid: string) {
this.metadata.labels = {
...this.metadata.labels,
[ResourceLabels.APP_ID]: appid,
}
}

get appid() {
return this.metadata.labels?.[ResourceLabels.APP_ID]
}

setUserId(userid: string) {
this.metadata.labels = {
...this.metadata.labels,
Expand Down
44 changes: 36 additions & 8 deletions server/src/collections/collections.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,68 @@ import {
Patch,
Param,
Delete,
Logger,
UseGuards,
} from '@nestjs/common'
import { ApiResponse, ApiTags } from '@nestjs/swagger'
import { ApplicationAuthGuard } from 'src/applications/application.auth.guard'
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard'
import { ResponseUtil } from 'src/common/response'
import { CollectionsService } from './collections.service'
import { CreateCollectionDto } from './dto/create-collection.dto'
import { UpdateCollectionDto } from './dto/update-collection.dto'

@Controller('collections')
@ApiTags('Database')
@Controller('apps/:appid/collections')
export class CollectionsController {
constructor(private readonly collectionsService: CollectionsService) {}
private readonly logger = new Logger(CollectionsController.name)
constructor(private readonly collectionService: CollectionsService) {}

/**
* Create a new collection in database
* @param appid
* @param dto
* @returns
*/
@ApiResponse({ type: ResponseUtil<boolean> })
@UseGuards(JwtAuthGuard, ApplicationAuthGuard)
@Post()
create(@Body() createCollectionDto: CreateCollectionDto) {
return this.collectionsService.create(createCollectionDto)
async create(
@Param('appid') appid: string,
@Body() dto: CreateCollectionDto,
) {
const error = await dto.validate()
if (error) {
return ResponseUtil.error(error)
}

const ok = await this.collectionService.create(appid, dto)
if (ok) {
return ResponseUtil.ok(ok)
}
return ResponseUtil.error('failed to create collection')
}

@Get()
findAll() {
return this.collectionsService.findAll()
return this.collectionService.findAll()
}

@Get(':id')
findOne(@Param('id') id: string) {
return this.collectionsService.findOne(+id)
return this.collectionService.findOne(+id)
}

@Patch(':id')
update(
@Param('id') id: string,
@Body() updateCollectionDto: UpdateCollectionDto,
) {
return this.collectionsService.update(+id, updateCollectionDto)
return this.collectionService.update(+id, updateCollectionDto)
}

@Delete(':id')
remove(@Param('id') id: string) {
return this.collectionsService.remove(+id)
return this.collectionService.remove(+id)
}
}
4 changes: 3 additions & 1 deletion server/src/collections/collections.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Module } from '@nestjs/common'
import { CollectionsService } from './collections.service'
import { CollectionsController } from './collections.controller'
import { DatabaseService } from './database.service'
import { ApplicationsService } from 'src/applications/applications.service'

@Module({
controllers: [CollectionsController],
providers: [CollectionsService],
providers: [CollectionsService, DatabaseService, ApplicationsService],
})
export class CollectionsModule {}
28 changes: 25 additions & 3 deletions server/src/collections/collections.service.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
import { Injectable } from '@nestjs/common'
import { Injectable, Logger } from '@nestjs/common'
import { DatabaseService } from './database.service'
import { CreateCollectionDto } from './dto/create-collection.dto'
import { UpdateCollectionDto } from './dto/update-collection.dto'
import * as assert from 'node:assert'

@Injectable()
export class CollectionsService {
create(createCollectionDto: CreateCollectionDto) {
return 'This action adds a new collection'
private readonly logger = new Logger(CollectionsService.name)
constructor(private readonly databaseService: DatabaseService) {}

/**
* Create collection in database
* @param appid
* @param dto
* @returns
*/
async create(appid: string, dto: CreateCollectionDto) {
assert(appid, 'appid is required')
const { client, db } = await this.databaseService.findAndConnect(appid)
assert(db, 'Database not found')
try {
await db.createCollection(dto.name)
await client.close()
return true
} catch (error) {
this.logger.error(error)
await client.close()
return false
}
}

findAll() {
Expand Down
18 changes: 18 additions & 0 deletions server/src/collections/database.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing'
import { DatabaseService } from './database.service'

describe('DatabaseService', () => {
let service: DatabaseService

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [DatabaseService],
}).compile()

service = module.get<DatabaseService>(DatabaseService)
})

it('should be defined', () => {
expect(service).toBeDefined()
})
})
78 changes: 78 additions & 0 deletions server/src/collections/database.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Injectable, Logger } from '@nestjs/common'
import { GetApplicationNamespaceById } from 'src/common/getter'
import { KubernetesService } from 'src/core/kubernetes.service'
import { Database } from './entities/database.entity'
import { MongoClient } from 'mongodb'
import * as assert from 'node:assert'

@Injectable()
export class DatabaseService {
private readonly logger = new Logger(DatabaseService.name)

constructor(private readonly k8sService: KubernetesService) {}

/**
* Find a database and connect to it
* @param appid
* @returns
*/
async findAndConnect(appid: string) {
const database = await this.findOne(appid)
assert(database, 'Database not found')

const client = await this.connectDatabase(database)
const db = client.db(database.metadata.name)
return { db, client }
}

/**
* Get the database of an app
* @param appid
* @returns
*/
async findOne(appid: string) {
assert(appid, 'appid is required')
const namespace = GetApplicationNamespaceById(appid)
const name = appid
try {
const res =
await this.k8sService.customObjectApi.getNamespacedCustomObject(
Database.GVK.group,
Database.GVK.version,
namespace,
Database.GVK.plural,
name,
)
return res.body as Database
} catch (err) {
this.logger.error(err)
if (err?.response?.body?.reason === 'NotFound') {
return null
}
throw err
}
}

/**
* Connect to database
*/
async connectDatabase(db: Database) {
assert(db, 'Database is required')
assert(db.status, 'Database status is required')
assert(db.status.connectionUri, 'Database connection uri is required')

const uri = db.status?.connectionUri
const client = new MongoClient(uri)
try {
await client.connect()
this.logger.log(`Connected to database ${db.metadata.namespace}`)
return client
} catch {
this.logger.error(
`Failed to connect to database ${db.metadata.namespace}`,
)
await client.close()
return null
}
}
}
15 changes: 14 additions & 1 deletion server/src/collections/dto/create-collection.dto.ts
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
export class CreateCollectionDto {}
import { ApiProperty } from '@nestjs/swagger'

export class CreateCollectionDto {
@ApiProperty()
name: string

async validate() {
if (!this.name) {
return 'Collection name is required'
}

return null
}
}
77 changes: 77 additions & 0 deletions server/src/collections/entities/database.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { KubernetesObject } from '@kubernetes/client-node'
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
import {
Condition,
GroupVersionKind,
ObjectMeta,
} from '../../core/kubernetes.interface'

export class DatabaseCapacity {
@ApiProperty()
storage: string
}

export class DatabaseSpec {
@ApiProperty()
provider: string

@ApiProperty()
region: string

@ApiProperty()
capacity: DatabaseCapacity

@ApiProperty()
username: string

@ApiProperty()
password: string
}

export class DatabaseStatus {
@ApiProperty()
storeName: string

@ApiProperty()
storeNamespace: string

@ApiProperty()
connectionUri: string

@ApiProperty()
capacity: DatabaseCapacity

@ApiProperty()
conditions: Condition[]
}

export class Database implements KubernetesObject {
@ApiProperty()
apiVersion: string

@ApiProperty()
kind: string

@ApiProperty()
metadata: ObjectMeta

@ApiProperty()
spec: DatabaseSpec

@ApiPropertyOptional()
status?: DatabaseStatus

static readonly GVK = new GroupVersionKind(
'database.laf.dev',
'v1',
'Database',
'databases',
)

constructor(name: string, namespace: string) {
this.apiVersion = Database.GVK.apiVersion
this.kind = Database.GVK.kind
this.metadata = new ObjectMeta(name, namespace)
this.spec = new DatabaseSpec()
}
}

0 comments on commit 6d98a4f

Please sign in to comment.