feat(export-import): add yaml export/import with id upsert for credentials, snippets, and scenarios
- all entities export a kind field (credential/snippet/scenario) for safe type checking on import - import upserts by id: overwrites if id exists, creates with explicit id otherwise - scenario export now includes id and step ids; import deletes old steps before recreating - add GET /:id/export and POST /import endpoints to credential and snippet controllers - add UuidBadge ui component: shortens uuid to first+last 4 hex chars, tooltip, clipboard copy with animated ClipboardCheck icon pop - apply UuidBadge across all entity id display sites (detail pages, card footers, table columns) - add export/import buttons to CredentialsPage, SnippetsPage, CredentialDetailPage, SnippetDetailPage
This commit is contained in:
@@ -8,6 +8,7 @@ import { Repository } from "typeorm";
|
||||
import { SnippetEntity } from "./snippet.entity";
|
||||
import { CreateSnippetDto } from "./dto/create-snippet.dto";
|
||||
import { UpdateSnippetDto } from "./dto/update-snippet.dto";
|
||||
import { SnippetExportDto } from "./dto/snippet-export.dto";
|
||||
import {
|
||||
PaginationQueryDto,
|
||||
PaginatedResult,
|
||||
@@ -47,13 +48,13 @@ export class SnippetService {
|
||||
return { data, total, page, limit };
|
||||
}
|
||||
|
||||
async findOne(id: number): Promise<SnippetEntity> {
|
||||
async findOne(id: string): Promise<SnippetEntity> {
|
||||
const snippet = await this.repo.findOneBy({ id });
|
||||
if (!snippet) throw new NotFoundException(`Snippet ${id} not found`);
|
||||
return snippet;
|
||||
}
|
||||
|
||||
async update(id: number, dto: UpdateSnippetDto): Promise<SnippetEntity> {
|
||||
async update(id: string, dto: UpdateSnippetDto): Promise<SnippetEntity> {
|
||||
const snippet = await this.findOne(id);
|
||||
if (dto.name && dto.name !== snippet.name) {
|
||||
const conflict = await this.repo.findOneBy({ name: dto.name });
|
||||
@@ -63,7 +64,7 @@ export class SnippetService {
|
||||
return this.repo.save(snippet);
|
||||
}
|
||||
|
||||
async remove(id: number): Promise<void> {
|
||||
async remove(id: string): Promise<void> {
|
||||
await this.findOne(id);
|
||||
await this.repo.delete(id);
|
||||
}
|
||||
@@ -73,4 +74,43 @@ export class SnippetService {
|
||||
const { data } = await this.findAll({ limit: 1000 });
|
||||
return Object.fromEntries(data.map((s) => [s.name, s.code]));
|
||||
}
|
||||
|
||||
exportSnippet(snippet: SnippetEntity): SnippetExportDto {
|
||||
return {
|
||||
kind: "snippet",
|
||||
id: snippet.id,
|
||||
name: snippet.name,
|
||||
description: snippet.description,
|
||||
code: snippet.code,
|
||||
};
|
||||
}
|
||||
|
||||
async importSnippet(dto: SnippetExportDto): Promise<SnippetEntity> {
|
||||
if (dto.id) {
|
||||
const existing = await this.repo.findOneBy({ id: dto.id });
|
||||
if (existing) {
|
||||
Object.assign(existing, {
|
||||
name: dto.name,
|
||||
description: dto.description ?? null,
|
||||
code: dto.code,
|
||||
});
|
||||
return this.repo.save(existing);
|
||||
}
|
||||
return this.repo.save(
|
||||
this.repo.create({
|
||||
id: dto.id,
|
||||
name: dto.name,
|
||||
description: dto.description ?? null,
|
||||
code: dto.code,
|
||||
}),
|
||||
);
|
||||
}
|
||||
return this.repo.save(
|
||||
this.repo.create({
|
||||
name: dto.name,
|
||||
description: dto.description ?? null,
|
||||
code: dto.code,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user