17 Commits

Author SHA1 Message Date
8b77f7e42c chore: update subproject commit reference in sdk 2025-10-29 17:06:47 +02:00
9e79b44bdb feat: implement session management with SessionController and SessionService; enhance session update logic to handle non-existent sessions 2025-10-29 17:06:38 +02:00
7fad278d31 chore: update subproject commit reference in sdk 2025-10-28 21:00:22 +02:00
776d8a8187 feat: update deleteSetting endpoint to use path parameter for key 2025-10-28 20:43:00 +02:00
e4b29a918f Merge remote-tracking branch 'origin/projectDetails' into sdk 2025-10-28 20:40:58 +02:00
4acd59b482 chore: update subproject commit reference in sdk 2025-10-28 20:37:22 +02:00
84c48dd482 feat: add getProjectInfo method and corresponding endpoint in ProjectController; refactor logger methods to include projectId 2025-10-28 20:36:53 +02:00
bbc378dc95 feat: add isPublic field to Query entity and implement QueryPublicGuard for public query access control 2025-10-28 16:35:26 +02:00
5d596832d6 feat: add optional isTypescript parameter to create and update query methods 2025-10-28 15:40:03 +02:00
3a1249615e feat: add TypeScript compilation support in QueryExecuterService 2025-10-28 15:37:09 +02:00
0ac6b7db6f chore: update subproject commit reference in sdk 2025-10-27 20:07:34 +02:00
9080f193c1 fix: update revokeToken method to use @Param decorator for token retrieval 2025-10-27 20:07:26 +02:00
1a2d7b20c0 feat: add submodule configuration for few-line-sdk 2025-10-27 19:20:15 +02:00
e1fce6d11d chore: remove submodule configuration for few-line-sdk 2025-10-27 19:13:37 +02:00
1d5160e60e chore: remove submodule reference for lib 2025-10-27 19:11:41 +02:00
038f2f8605 fix: remove unnecessary blank line in ProjectService 2025-10-27 19:08:56 +02:00
91ad421b8d feat: add submodule for few-line-sdk library 2025-10-27 19:08:40 +02:00
15 changed files with 239 additions and 43 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "lib/sdk"]
path = lib/sdk
url = http://192.168.0.16:3000/lborv/few-line-sdk.git

1
lib/sdk Submodule

Submodule lib/sdk added at 4b8c6753ef

View File

@ -3,6 +3,7 @@ import {
Controller,
Delete,
Inject,
Param,
Post,
UseGuards,
} from "@nestjs/common";
@ -28,8 +29,8 @@ export class ApiController {
return this.apiService.generateToken(body.id);
}
@Delete("token/revoke")
revokeToken(@Body() body: { token: string }) {
return this.apiService.revokeToken(body.token);
@Delete("token/revoke/:token")
revokeToken(@Param("token") token: string) {
return this.apiService.revokeToken(token);
}
}

View File

@ -4,6 +4,7 @@ import {
Delete,
Get,
Inject,
Param,
Post,
Put,
Req,
@ -27,6 +28,7 @@ export class ProjectController {
) {}
@Put("create")
@UseGuards(AdminGuard)
createProject(@Body() body: { name: string }) {
return this.projectService.create(body.name);
}
@ -64,12 +66,12 @@ export class ProjectController {
);
}
@Delete("settings/delete")
@Delete("settings/delete/:key")
deleteSetting(
@Body() body: { key: string },
@Param("key") key: string,
@Req() req: Request & { apiToken: { id: string } }
) {
return this.projectSettingService.delete(req.apiToken.id, body.key);
return this.projectSettingService.delete(req.apiToken.id, key);
}
@Get("settings")

View File

@ -112,4 +112,13 @@ export class ProjectService {
});
return project?.apiTokens || [];
}
async getProjectInfo(projectId: string) {
const project = await this.projectRepository.findOne({
where: { id: projectId },
relations: ["queries", "apiTokens", "functions", "settings"],
});
return project;
}
}

View File

@ -19,8 +19,8 @@ import { TLogType } from "../logger/logger.types";
import { QueryResponse } from "src/vm/vm.constants";
import { Query } from "../entities/query.entity";
import { Token } from "src/api/entities/token.entity";
import { QueryPublicGuard } from "../guards/query-public";
@UseGuards(ApiTokenGuard)
export abstract class BaseQueryController {
constructor(
@Inject(QueryHandlerService)
@ -34,9 +34,11 @@ export abstract class BaseQueryController {
protected abstract getIsCommand(): boolean;
@Post("create")
@UseGuards(ApiTokenGuard)
async createQuery(
@Req() req: Request & { apiToken: Token },
@Body() queryData: { source: string }
@Body()
queryData: { source: string; isTypescript?: number; isPublic?: number }
) {
return this.queryHandlerService.createQuery(
{
@ -48,23 +50,26 @@ export abstract class BaseQueryController {
}
@Post("update/:id")
@UseGuards(QueryGuard)
@UseGuards(ApiTokenGuard, QueryGuard)
async updateQuery(
@Body() updateData: Partial<{ source: string }>,
@Body()
updateData: Partial<{
source: string;
isTypescript?: number;
isPublic?: number;
}>,
@Param("id") id: string
) {
return this.queryHandlerService.updateQuery(id, updateData);
}
@Post("/run/:id")
@UseGuards(QueryGuard)
async runQuery(
@Param("id") id: string,
@Body() query: Record<string, any>,
@Headers() headers: Record<string, any>,
@Res() res: Response,
@Req() req: Request & { query: Query }
) {
private async run(
id: string,
query: Record<string, any>,
headers: Record<string, any>,
res: Response,
req: Request & { query: Query }
): Promise<QueryResponse> {
let queryResult: QueryResponse;
const loggerTraceId =
headers["x-trace-id"] || LoggerService.generateTraceId();
@ -143,8 +148,32 @@ export abstract class BaseQueryController {
res.send(queryResult?.response || null);
}
@Post("/run-public/:id")
@UseGuards(QueryPublicGuard)
async runPublicQuery(
@Param("id") id: string,
@Body() query: Record<string, any>,
@Headers() headers: Record<string, any>,
@Res() res: Response,
@Req() req: Request & { query: Query }
) {
return this.run(id, query, headers, res, req);
}
@Post("/run/:id")
@UseGuards(ApiTokenGuard, QueryGuard)
async runQuery(
@Param("id") id: string,
@Body() query: Record<string, any>,
@Headers() headers: Record<string, any>,
@Res() res: Response,
@Req() req: Request & { query: Query }
) {
return this.run(id, query, headers, res, req);
}
@Delete("/delete/:id")
@UseGuards(QueryGuard)
@UseGuards(ApiTokenGuard, QueryGuard)
async deleteQuery(@Param("id") id: string) {
return this.queryHandlerService.deleteQuery(id);
}

View File

@ -27,4 +27,10 @@ export class Query {
@Column({ type: "tinyint", default: 0 })
isCommand: number;
@Column({ type: "tinyint", default: 0 })
isTypescript: number;
@Column({ type: "tinyint", default: 0 })
isPublic: number;
}

View File

@ -19,6 +19,7 @@ import { SessionService } from "../session/session.service";
import { TLog, TLogType } from "../logger/logger.types";
import { LoggerService } from "../logger/logger.service";
import { ProjectSettingService } from "src/project/settings/project.setting.service";
import ts from "typescript";
@Injectable()
export class QueryExecuterService {
@ -89,6 +90,14 @@ export class QueryExecuterService {
return result;
}
private compileTypeScript(tsCode: string) {
const jsCode = ts.transpileModule(tsCode, {
compilerOptions: { module: ts.ModuleKind.CommonJS },
}).outputText;
return jsCode;
}
async runQuery(
token: string,
queryData: any,
@ -122,17 +131,19 @@ export class QueryExecuterService {
type: TLogType.info,
});
let script = this.clearImports(query.source);
if (query.isTypescript) {
script = this.compileTypeScript(script);
}
const vm = await this.createVm(
query,
log,
callStack,
cookies["x-session-id"]
);
const result = await vm.runScript(
this.clearImports(query.source),
queryData,
headers
);
const result = await vm.runScript(script, queryData, headers);
if (!this.checkResponse(result)) {
throw new Error(`Error initializing VM: ${JSON.stringify(result)}`);

View File

@ -1,4 +1,12 @@
import { Controller, Inject, Post, Req, UseGuards } from "@nestjs/common";
import {
Controller,
Delete,
Inject,
Param,
Post,
Req,
UseGuards,
} from "@nestjs/common";
import { ApiTokenGuard } from "src/api/guards/api-token.guard";
import { FunctionService } from "./function.service";
import { Token } from "src/api/entities/token.entity";
@ -20,10 +28,10 @@ export class FunctionController {
return this.functionService.create(req.apiToken.project.id, name, source);
}
@Post("delete")
@Delete("delete/:name")
async deleteFunction(
@Req() req: Request & { apiToken: Token },
name: string
@Param("name") name: string
) {
return this.functionService.deleteFunction(req.apiToken.project.id, name);
}

View File

@ -0,0 +1,44 @@
import {
CanActivate,
ExecutionContext,
Inject,
Injectable,
UnauthorizedException,
} from "@nestjs/common";
import { QueryHandlerService } from "src/query/handler/query.handler.service";
@Injectable()
export class QueryPublicGuard implements CanActivate {
constructor(
@Inject(QueryHandlerService)
private readonly queryHandlerService: QueryHandlerService
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const queryId = request.params?.id;
if (!queryId) {
throw new UnauthorizedException("Query ID is required");
}
const query = await this.queryHandlerService.getQueryById(queryId);
if (!query) {
throw new UnauthorizedException("Query not found");
}
if (!query.isActive) {
throw new UnauthorizedException("Query is inactive");
}
if (query.isPublic !== 1) {
throw new UnauthorizedException("Query is not public");
}
request.query = query;
return true;
}
}

View File

@ -31,7 +31,6 @@ export class QueryHandlerService {
delete queryData.projectToken;
const query = this.queryRepository.create(queryData);
await this.queryRepository.save(query);
return query;
@ -64,9 +63,9 @@ export class QueryHandlerService {
}
await this.redisClient.del(`query_${id}`);
Object.assign(query, updateData);
return this.queryRepository.save(query);
return await this.queryRepository.save(query);
}
async deleteQuery(id: string) {

View File

@ -5,11 +5,14 @@ import {
Inject,
Param,
Post,
Req,
UseGuards,
} from "@nestjs/common";
import { LoggerService } from "./logger.service";
import { ApiTokenGuard } from "src/api/guards/api-token.guard";
import { QueryGuard } from "../guards/query.guard";
import { Token } from "src/api/entities/token.entity";
import { Query } from "../entities/query.entity";
@Controller("logger")
@UseGuards(ApiTokenGuard)
@ -19,14 +22,17 @@ export class LoggerController {
private readonly loggerService: LoggerService
) {}
@Get("/:id/:traceId")
getByTraceId(@Param("traceId") traceId: string) {
return this.loggerService.findByTraceId(traceId);
@Get("/:traceId")
getByTraceId(
@Req() req: Request & { apiToken: Token },
@Param("traceId") traceId: string
) {
return this.loggerService.findByTraceId(req.apiToken.project.id, traceId);
}
@Post("/:id/findAll")
@Post("/findAll")
findAll(
@Param("id") projectId: string,
@Req() req: Request & { apiToken: Token },
@Body()
body: {
traceId?: string;
@ -37,13 +43,13 @@ export class LoggerController {
offset: number;
}
) {
return this.loggerService.findByProjectId(projectId, body);
return this.loggerService.findByProjectId(req.apiToken.project.id, body);
}
@Post("/:id/find")
@Post("/find")
@UseGuards(QueryGuard)
find(
@Param("id") _id: string,
@Req() req: Request & { query: Query },
@Body()
body: {
traceId?: string;
@ -54,6 +60,6 @@ export class LoggerController {
offset: number;
}
) {
return this.loggerService.find(_id, body);
return this.loggerService.find(req.query.id, body);
}
}

View File

@ -22,8 +22,15 @@ export class LoggerService {
return await this.logRepository.save(log);
}
async findByTraceId(traceId: string): Promise<Log[]> {
return await this.logRepository.find({ where: { traceId } });
async findByTraceId(projectId: string, traceId: string): Promise<Log[]> {
return await this.logRepository.find({
where: {
traceId,
project: {
id: projectId,
},
},
});
}
private prepareQuery(data: {

View File

@ -0,0 +1,59 @@
import {
Body,
Controller,
Delete,
Get,
Inject,
Param,
Post,
Put,
Req,
UseGuards,
} from "@nestjs/common";
import { ApiTokenGuard } from "src/api/guards/api-token.guard";
import { SessionService } from "./session.service";
@Controller("session")
@UseGuards(ApiTokenGuard)
export class SessionController {
constructor(
@Inject(SessionService)
private readonly sessionService: SessionService
) {}
@Get("get/:sessionId")
getSession(
@Param("sessionId") sessionId: string,
@Req() req: Request & { apiToken: { project: { id: string } } }
) {
return this.sessionService.get(sessionId, req.apiToken.project.id);
}
@Delete("delete/:sessionId")
deleteSession(
@Param("sessionId") sessionId: string,
@Req() req: Request & { apiToken: { project: { id: string } } }
) {
return this.sessionService.delete(sessionId, req.apiToken.project.id);
}
@Post("create")
createSession(
@Req() req: Request & { apiToken: { project: { id: string } } }
) {
return this.sessionService.create(req.apiToken.project.id);
}
@Put("set/:sessionId")
setSession(
@Param("sessionId") sessionId: string,
@Req() req: Request & { apiToken: { project: { id: string } } },
@Body() body: { data: any }
) {
return this.sessionService.set(
sessionId,
req.apiToken.project.id,
body.data
);
}
}

View File

@ -56,9 +56,20 @@ export class SessionService {
}
async set(sessionId: string, prefix: string, data: any): Promise<void> {
const existingSession = await this.redisClient.get(
`${prefix}:${sessionId}`
);
if (!existingSession) {
throw new Error("Session not found");
}
await this.redisClient.set(
`${prefix}:${sessionId}`,
data,
{
...existingSession,
...data,
},
await this.getSessionTTL(prefix)
);
}