feat(app): add environment import/export and sidebar footer info

- add environment import/export API endpoints and client integration

- add environment export actions and toolbar import/export buttons in UI

- move theme switcher to sidebar bottom and show root package version

- switch snippet card menu icon to cog for consistent options affordance
This commit is contained in:
2026-04-10 20:29:40 +03:00
parent 99da7ac891
commit 0e4a2e1819
13 changed files with 218 additions and 20 deletions
+1
View File
@@ -1,5 +1,6 @@
{
"name": "liqa-server",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"build": "nest build",
@@ -0,0 +1,31 @@
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import {
IsIn,
IsNotEmpty,
IsObject,
IsOptional,
IsString,
IsUUID,
} from "class-validator";
import { EnvironmentUrls } from "../environment.entity";
export class EnvironmentExportDto {
@ApiPropertyOptional()
@IsOptional()
@IsIn(["environment"])
kind?: "environment";
@ApiPropertyOptional()
@IsOptional()
@IsUUID()
id?: string;
@ApiProperty()
@IsString()
@IsNotEmpty()
name: string;
@ApiProperty()
@IsObject()
urls: EnvironmentUrls;
}
@@ -14,6 +14,7 @@ import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
import { EnvironmentService } from "./environment.service";
import { CreateEnvironmentDto } from "./dto/create-environment.dto";
import { UpdateEnvironmentDto } from "./dto/update-environment.dto";
import { EnvironmentExportDto } from "./dto/environment-export.dto";
import { PaginationQueryDto } from "../common/dto/pagination.dto";
import { EnvironmentOrderBy } from "./environment.service";
@@ -30,6 +31,13 @@ export class EnvironmentController {
return this.environmentService.create(dto);
}
@Post("import")
@ApiOperation({ summary: "Import an environment (upsert by id or name)" })
@ApiResponse({ status: 201, description: "Environment imported" })
async importEnvironment(@Body() dto: EnvironmentExportDto) {
return this.environmentService.importEnvironment(dto);
}
@Get()
@ApiOperation({ summary: "List all environments (paginated)" })
@ApiResponse({ status: 200, description: "Paginated environments" })
@@ -37,6 +45,15 @@ export class EnvironmentController {
return this.environmentService.findAll(query);
}
@Get(":id/export")
@ApiOperation({ summary: "Export an environment as a plain object" })
@ApiResponse({ status: 200, description: "Environment export payload" })
@ApiResponse({ status: 404, description: "Environment not found" })
async exportEnvironment(@Param("id", ParseUUIDPipe) id: string) {
const env = await this.environmentService.findOne(id);
return this.environmentService.exportEnvironment(env);
}
@Get(":id")
@ApiOperation({ summary: "Get environment by ID" })
@ApiResponse({ status: 200, description: "Environment record" })
@@ -8,6 +8,7 @@ import { Repository } from "typeorm";
import { EnvironmentEntity } from "./environment.entity";
import { CreateEnvironmentDto } from "./dto/create-environment.dto";
import { UpdateEnvironmentDto } from "./dto/update-environment.dto";
import { EnvironmentExportDto } from "./dto/environment-export.dto";
import {
PaginationQueryDto,
PaginatedResult,
@@ -64,4 +65,32 @@ export class EnvironmentService {
await this.findOne(id);
await this.repo.delete(id);
}
exportEnvironment(env: EnvironmentEntity): EnvironmentExportDto {
return {
kind: "environment",
id: env.id,
name: env.name,
urls: env.urls,
};
}
async importEnvironment(dto: EnvironmentExportDto): Promise<EnvironmentEntity> {
if (dto.id) {
const existing = await this.repo.findOneBy({ id: dto.id });
if (existing) {
Object.assign(existing, { name: dto.name, urls: dto.urls });
return this.repo.save(existing);
}
return this.repo.save(
this.repo.create({ id: dto.id, name: dto.name, urls: dto.urls }),
);
}
const byName = await this.repo.findOneBy({ name: dto.name });
if (byName) {
Object.assign(byName, { urls: dto.urls });
return this.repo.save(byName);
}
return this.repo.save(this.repo.create({ name: dto.name, urls: dto.urls }));
}
}