Files
liqa/server/src/credential/credential.service.ts
T
ars9 32be7c0a59 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
2026-04-10 13:09:09 +03:00

78 lines
2.6 KiB
TypeScript

import { Injectable, NotFoundException } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { CredentialEntity } from "./credential.entity";
import { CreateCredentialDto } from "./dto/create-credential.dto";
import { UpdateCredentialDto } from "./dto/update-credential.dto";
import { CredentialExportDto } from "./dto/credential-export.dto";
import {
PaginationQueryDto,
PaginatedResult,
} from "../common/dto/pagination.dto";
export type CredentialOrderBy = "id" | "name" | "lastUsedAt" | "createdAt" | "updatedAt";
@Injectable()
export class CredentialService {
constructor(
@InjectRepository(CredentialEntity)
private readonly repo: Repository<CredentialEntity>,
) {}
async create(dto: CreateCredentialDto): Promise<CredentialEntity> {
return this.repo.save(this.repo.create({ ...dto, data: dto.data ?? null }));
}
async findAll(
query: PaginationQueryDto<CredentialOrderBy> = {},
): Promise<PaginatedResult<CredentialEntity>> {
const page = query.page ?? 1;
const limit = query.limit ?? 20;
const orderBy = query.orderBy ?? "id";
const orderDir = query.orderDir ?? "ASC";
const [data, total] = await this.repo.findAndCount({
order: { [orderBy]: orderDir },
skip: (page - 1) * limit,
take: limit,
});
return { data, total, page, limit };
}
async findOne(id: string): Promise<CredentialEntity> {
const credential = await this.repo.findOneBy({ id });
if (!credential) throw new NotFoundException(`Credential ${id} not found`);
return credential;
}
async update(id: string, dto: UpdateCredentialDto): Promise<CredentialEntity> {
const credential = await this.findOne(id);
Object.assign(credential, dto);
return this.repo.save(credential);
}
async remove(id: string): Promise<void> {
await this.findOne(id);
await this.repo.delete(id);
}
exportCredential(credential: CredentialEntity): CredentialExportDto {
return { kind: "credential", id: credential.id, name: credential.name, data: credential.data };
}
async importCredential(dto: CredentialExportDto): Promise<CredentialEntity> {
if (dto.id) {
const existing = await this.repo.findOneBy({ id: dto.id });
if (existing) {
Object.assign(existing, { name: dto.name, data: dto.data ?? null });
return this.repo.save(existing);
}
return this.repo.save(
this.repo.create({ id: dto.id, name: dto.name, data: dto.data ?? null }),
);
}
return this.repo.save(
this.repo.create({ name: dto.name, data: dto.data ?? null }),
);
}
}