diff --git a/server/src/applications/applications.controller.ts b/server/src/applications/applications.controller.ts index ffc7dfb641..9cad8765d4 100644 --- a/server/src/applications/applications.controller.ts +++ b/server/src/applications/applications.controller.ts @@ -30,7 +30,6 @@ export class ApplicationsController { * Create application * @returns */ - // @ApiResponse({ type: ResponseUtil }) @ApiResponseUtil(Application) @UseGuards(JwtAuthGuard) @Post() diff --git a/server/src/applications/applications.service.ts b/server/src/applications/applications.service.ts index 9a8dbac4bf..56551c96f8 100644 --- a/server/src/applications/applications.service.ts +++ b/server/src/applications/applications.service.ts @@ -46,13 +46,11 @@ export class ApplicationsService { async create(userid: string, appid: string, dto: CreateApplicationDto) { // create app resources - const app = new Application(dto.name, appid) - app.metadata.name = dto.name - app.metadata.namespace = appid - app.metadata.labels = { - [ResourceLabels.APP_ID]: appid, - [ResourceLabels.USER_ID]: userid, - } + const namespace = GetApplicationNamespaceById(appid) + const app = new Application(dto.name, namespace) + app.setAppid(appid) + app.setUserId(userid) + app.spec = new ApplicationSpec({ appid, state: dto.state, @@ -61,8 +59,6 @@ export class ApplicationsService { runtimeName: dto.runtimeName, }) - console.log(app) - try { const res = await this.k8sClient.objectApi.create(app) return res.body diff --git a/server/src/applications/entities/application.entity.ts b/server/src/applications/entities/application.entity.ts index 2c608b1835..2025456d12 100644 --- a/server/src/applications/entities/application.entity.ts +++ b/server/src/applications/entities/application.entity.ts @@ -1,5 +1,6 @@ import { KubernetesObject } from '@kubernetes/client-node' import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' +import { ResourceLabels } from 'src/constants' import { Condition, ObjectMeta } from '../../core/kubernetes.interface' import { BundleSpec } from './bundle.entity' import { RuntimeSpec } from './runtime.entity' @@ -94,12 +95,32 @@ export class Application implements KubernetesObject { constructor(name: string, namespace: string) { this.apiVersion = Application.GroupVersion this.kind = Application.Kind - this.metadata = { - name, - namespace, - } + this.metadata = new ObjectMeta(name, namespace) + this.metadata.labels = {} 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, + [ResourceLabels.USER_ID]: userid, + } + } + + get userid() { + return this.metadata.labels?.[ResourceLabels.USER_ID] + } } export class ApplicationList { @ApiProperty() diff --git a/server/src/functions/dto/create-function.dto.ts b/server/src/functions/dto/create-function.dto.ts index 868f8de4b0..5e0226e956 100644 --- a/server/src/functions/dto/create-function.dto.ts +++ b/server/src/functions/dto/create-function.dto.ts @@ -1 +1,39 @@ -export class CreateFunctionDto {} +import { ApiProperty } from '@nestjs/swagger' + +export class CreateFunctionDto { + @ApiProperty({ + description: 'Function name is unique in the application', + }) + name: string + + @ApiProperty() + description: string + + @ApiProperty() + websocket: boolean + + @ApiProperty({ + enum: ['HEAD', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH'], + }) + methods: string[] = [] + + @ApiProperty({ + description: 'The source code of the function', + }) + codes: string + + validate() { + if (!this.name) { + return 'name is required' + } + + const valid = this.methods.every((method) => { + return ['HEAD', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(method) + }) + if (!valid) { + return 'methods is invalid' + } + + return null + } +} diff --git a/server/src/functions/entities/function.entity.ts b/server/src/functions/entities/function.entity.ts index 543507dd8b..839b178d08 100644 --- a/server/src/functions/entities/function.entity.ts +++ b/server/src/functions/entities/function.entity.ts @@ -1 +1,95 @@ -export class Function {} +import { KubernetesListObject, KubernetesObject } from '@kubernetes/client-node' +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' +import { ObjectMeta } from 'src/core/kubernetes.interface' + +export class CloudFunctionSource { + @ApiProperty() + codes: string + + @ApiProperty() + uri?: string + + @ApiProperty() + version: number + + constructor() { + this.version = 0 + } +} + +export class CloudFunctionSpec { + @ApiProperty() + description: string + + @ApiProperty() + websocket: boolean + + @ApiProperty() + methods: string[] + + @ApiProperty() + source: CloudFunctionSource + + constructor() { + this.source = new CloudFunctionSource() + } +} + +export enum CloudFunctionState { + Deployed = 'Deployed', +} + +export class CloudFunctionStatus { + @ApiProperty({ + enum: CloudFunctionState, + }) + state: CloudFunctionState +} + +export class CloudFunction implements KubernetesObject { + @ApiProperty() + apiVersion: string + + @ApiProperty() + kind: string + + @ApiProperty() + metadata: ObjectMeta + + @ApiProperty() + spec: CloudFunctionSpec + + @ApiPropertyOptional() + status?: CloudFunctionStatus + + static readonly Group = 'runtime.laf.dev' + static readonly Version = 'v1' + static readonly PluralName = 'functions' + static readonly Kind = 'Function' + static get GroupVersion() { + return `${this.Group}/${this.Version}` + } + + constructor(name: string, namespace: string) { + this.apiVersion = CloudFunction.GroupVersion + this.kind = CloudFunction.Kind + this.metadata = { + name, + namespace, + } + this.spec = new CloudFunctionSpec() + } +} + +export class CloudFunctionList implements KubernetesListObject { + @ApiProperty() + apiVersion?: string + + @ApiProperty() + kind?: string = 'FunctionList' + + @ApiProperty({ + type: [CloudFunction], + }) + items: CloudFunction[] +} diff --git a/server/src/functions/functions.controller.ts b/server/src/functions/functions.controller.ts index 3e026bb7c1..b6ebe2474d 100644 --- a/server/src/functions/functions.controller.ts +++ b/server/src/functions/functions.controller.ts @@ -6,25 +6,48 @@ import { Patch, Param, Delete, + UseGuards, + Req, } from '@nestjs/common' import { FunctionsService } from './functions.service' import { CreateFunctionDto } from './dto/create-function.dto' import { UpdateFunctionDto } from './dto/update-function.dto' +import { ApiResponseUtil, ResponseUtil } from 'src/common/response' +import { CloudFunction, CloudFunctionList } from './entities/function.entity' +import { ApiTags } from '@nestjs/swagger' +import { JwtAuthGuard } from 'src/auth/jwt-auth.guard' +import { ApplicationAuthGuard } from 'src/applications/application.auth.guard' +import { IRequest } from 'src/common/types' -@Controller('functions') +@ApiTags('Functions') +@Controller('apps/:appid/functions') export class FunctionsController { constructor(private readonly functionsService: FunctionsService) {} + @ApiResponseUtil(CloudFunction) + @UseGuards(JwtAuthGuard, ApplicationAuthGuard) @Post() - create(@Body() createFunctionDto: CreateFunctionDto) { - return this.functionsService.create(createFunctionDto) + async create(@Body() dto: CreateFunctionDto, @Req() req: IRequest) { + const error = dto.validate() + if (error) { + return ResponseUtil.error(error) + } + + const appid = req.application.appid + const res = await this.functionsService.create(appid, dto) + if (!res) { + return ResponseUtil.error('create function error') + } + return ResponseUtil.ok(res) } + @ApiResponseUtil(CloudFunctionList) @Get() findAll() { return this.functionsService.findAll() } + @ApiResponseUtil(CloudFunction) @Get(':id') findOne(@Param('id') id: string) { return this.functionsService.findOne(+id) diff --git a/server/src/functions/functions.module.ts b/server/src/functions/functions.module.ts index a7998379ec..61afcaff97 100644 --- a/server/src/functions/functions.module.ts +++ b/server/src/functions/functions.module.ts @@ -1,9 +1,10 @@ import { Module } from '@nestjs/common' import { FunctionsService } from './functions.service' import { FunctionsController } from './functions.controller' +import { ApplicationsService } from 'src/applications/applications.service' @Module({ controllers: [FunctionsController], - providers: [FunctionsService], + providers: [FunctionsService, ApplicationsService], }) export class FunctionsModule {} diff --git a/server/src/functions/functions.service.ts b/server/src/functions/functions.service.ts index 44e64c1340..e97ee257bf 100644 --- a/server/src/functions/functions.service.ts +++ b/server/src/functions/functions.service.ts @@ -1,11 +1,31 @@ -import { Injectable } from '@nestjs/common' +import { Injectable, Logger } from '@nestjs/common' +import { GetApplicationNamespaceById } from 'src/common/getter' +import { KubernetesService } from 'src/core/kubernetes.service' import { CreateFunctionDto } from './dto/create-function.dto' import { UpdateFunctionDto } from './dto/update-function.dto' +import { CloudFunction } from './entities/function.entity' @Injectable() export class FunctionsService { - create(createFunctionDto: CreateFunctionDto) { - return 'This action adds a new function' + private logger = new Logger(FunctionsService.name) + + constructor(private readonly k8sClient: KubernetesService) {} + + async create(appid: string, dto: CreateFunctionDto) { + const namespace = GetApplicationNamespaceById(appid) + const func = new CloudFunction(dto.name, namespace) + func.spec.description = dto.description + func.spec.methods = dto.methods + func.spec.source.codes = dto.codes + func.spec.websocket = dto.websocket + + try { + const res = await this.k8sClient.objectApi.create(func) + return res.body + } catch (error) { + this.logger.error(error, error?.response.body) + return null + } } findAll() {