refactor(core): Update tag endpoints to use DTOs and injectable config (#12380)
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions

This commit is contained in:
Ricardo Espinoza 2025-01-09 14:17:11 -05:00 committed by GitHub
parent 95f055d23a
commit b1a40a231b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 282 additions and 103 deletions

View file

@ -44,3 +44,6 @@ export { CredentialsGetOneRequestQuery } from './credentials/credentials-get-one
export { CredentialsGetManyRequestQuery } from './credentials/credentials-get-many-request.dto';
export { ImportWorkflowFromUrlDto } from './workflows/import-workflow-from-url.dto';
export { CreateOrUpdateTagRequestDto } from './tag/create-or-update-tag-request.dto';
export { RetrieveTagQueryDto } from './tag/retrieve-tag-query.dto';

View file

@ -0,0 +1,37 @@
import { CreateOrUpdateTagRequestDto } from '../create-or-update-tag-request.dto';
describe('CreateOrUpdateTagRequestDto', () => {
describe('Valid requests', () => {
test.each([
{
name: 'valid name',
request: {
name: 'tag-name',
},
},
])('should validate $name', ({ request }) => {
const result = CreateOrUpdateTagRequestDto.safeParse(request);
expect(result.success).toBe(true);
});
});
describe('Invalid requests', () => {
test.each([
{
name: 'empty tag name',
request: {
name: '',
},
expectedErrorPath: ['name'],
},
])('should fail validation for $name', ({ request, expectedErrorPath }) => {
const result = CreateOrUpdateTagRequestDto.safeParse(request);
expect(result.success).toBe(false);
if (expectedErrorPath) {
expect(result.error?.issues[0].path).toEqual(expectedErrorPath);
}
});
});
});

View file

@ -0,0 +1,64 @@
import { RetrieveTagQueryDto } from '../retrieve-tag-query.dto';
describe('RetrieveTagQueryDto', () => {
describe('Valid requests', () => {
test.each([
{
name: 'with "true"',
request: {
withUsageCount: 'true',
},
},
{
name: 'with "false"',
request: {
withUsageCount: 'false',
},
},
])('should pass validation for withUsageCount $name', ({ request }) => {
const result = RetrieveTagQueryDto.safeParse(request);
expect(result.success).toBe(true);
});
});
describe('Invalid requests', () => {
test.each([
{
name: 'with number',
request: {
withUsageCount: 1,
},
expectedErrorPath: ['withUsageCount'],
},
{
name: 'with boolean (true) ',
request: {
withUsageCount: true,
},
expectedErrorPath: ['withUsageCount'],
},
{
name: 'with boolean (false)',
request: {
withUsageCount: false,
},
expectedErrorPath: ['withUsageCount'],
},
{
name: 'with invalid string',
request: {
withUsageCount: 'invalid',
},
expectedErrorPath: ['withUsageCount'],
},
])('should fail validation for withUsageCount $name', ({ request, expectedErrorPath }) => {
const result = RetrieveTagQueryDto.safeParse(request);
expect(result.success).toBe(false);
if (expectedErrorPath) {
expect(result.error?.issues[0].path).toEqual(expectedErrorPath);
}
});
});
});

View file

@ -0,0 +1,6 @@
import { z } from 'zod';
import { Z } from 'zod-class';
export class CreateOrUpdateTagRequestDto extends Z.class({
name: z.string().trim().min(1),
}) {}

View file

@ -0,0 +1,7 @@
import { Z } from 'zod-class';
import { booleanFromString } from '../../schemas/booleanFromString';
export class RetrieveTagQueryDto extends Z.class({
withUsageCount: booleanFromString.optional().default('false'),
}) {}

View file

@ -0,0 +1,10 @@
import { Config, Env } from '../decorators';
@Config
export class TagsConfig {
/*
Disable workflow tags
*/
@Env('N8N_WORKFLOW_TAGS_DISABLED')
disabled: boolean = false;
}

View file

@ -18,6 +18,7 @@ import { TaskRunnersConfig } from './configs/runners.config';
import { ScalingModeConfig } from './configs/scaling-mode.config';
import { SecurityConfig } from './configs/security.config';
import { SentryConfig } from './configs/sentry.config';
import { TagsConfig } from './configs/tags.config';
import { TemplatesConfig } from './configs/templates.config';
import { UserManagementConfig } from './configs/user-management.config';
import { VersionNotificationsConfig } from './configs/version-notifications.config';
@ -125,4 +126,7 @@ export class GlobalConfig {
@Nested
aiAssistant: AiAssistantConfig;
@Nested
tags: TagsConfig;
}

View file

@ -295,6 +295,9 @@ describe('GlobalConfig', () => {
aiAssistant: {
baseUrl: '',
},
tags: {
disabled: false,
},
};
it('should use all default values when no env variables are defined', () => {

View file

@ -139,13 +139,6 @@ export const schema = {
doc: 'Public URL where the editor is accessible. Also used for emails sent from n8n.',
},
workflowTagsDisabled: {
format: Boolean,
default: false,
env: 'N8N_WORKFLOW_TAGS_DISABLED',
doc: 'Disable workflow tags.',
},
userManagement: {
jwtSecret: {
doc: 'Set a specific JWT secret (optional - n8n can generate one)', // Generated @ start.ts

View file

@ -1,54 +1,60 @@
import { Request, Response, NextFunction } from 'express';
import { CreateOrUpdateTagRequestDto, RetrieveTagQueryDto } from '@n8n/api-types';
import { Response } from 'express';
import config from '@/config';
import { Delete, Get, Middleware, Patch, Post, RestController, GlobalScope } from '@/decorators';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { TagsRequest } from '@/requests';
import {
Delete,
Get,
Patch,
Post,
RestController,
GlobalScope,
Body,
Param,
Query,
} from '@/decorators';
import { AuthenticatedRequest } from '@/requests';
import { TagService } from '@/services/tag.service';
@RestController('/tags')
export class TagsController {
private config = config;
constructor(private readonly tagService: TagService) {}
// TODO: move this into a new decorator `@IfEnabled('workflowTagsDisabled')`
@Middleware()
workflowsEnabledMiddleware(_req: Request, _res: Response, next: NextFunction) {
if (this.config.getEnv('workflowTagsDisabled'))
throw new BadRequestError('Workflow tags are disabled');
next();
}
@Get('/')
@GlobalScope('tag:list')
async getAll(req: TagsRequest.GetAll) {
return await this.tagService.getAll({ withUsageCount: req.query.withUsageCount === 'true' });
async getAll(_req: AuthenticatedRequest, _res: Response, @Query query: RetrieveTagQueryDto) {
return await this.tagService.getAll({ withUsageCount: query.withUsageCount });
}
@Post('/')
@GlobalScope('tag:create')
async createTag(req: TagsRequest.Create) {
const tag = this.tagService.toEntity({ name: req.body.name });
async createTag(
_req: AuthenticatedRequest,
_res: Response,
@Body payload: CreateOrUpdateTagRequestDto,
) {
const { name } = payload;
const tag = this.tagService.toEntity({ name });
return await this.tagService.save(tag, 'create');
}
@Patch('/:id(\\w+)')
@GlobalScope('tag:update')
async updateTag(req: TagsRequest.Update) {
const newTag = this.tagService.toEntity({ id: req.params.id, name: req.body.name.trim() });
async updateTag(
_req: AuthenticatedRequest,
_res: Response,
@Param('id') tagId: string,
@Body payload: CreateOrUpdateTagRequestDto,
) {
const newTag = this.tagService.toEntity({ id: tagId, name: payload.name });
return await this.tagService.save(newTag, 'update');
}
@Delete('/:id(\\w+)')
@GlobalScope('tag:delete')
async deleteTag(req: TagsRequest.Delete) {
const { id } = req.params;
await this.tagService.delete(id);
async deleteTag(_req: AuthenticatedRequest, _res: Response, @Param('id') tagId: string) {
await this.tagService.delete(tagId);
return true;
}
}

View file

@ -12,7 +12,6 @@ import {
type FindOptionsRelations,
} from '@n8n/typeorm';
import config from '@/config';
import type { ListQuery } from '@/requests';
import { isStringArray } from '@/utils';
@ -132,7 +131,7 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
const relations: string[] = [];
const areTagsEnabled = !config.getEnv('workflowTagsDisabled');
const areTagsEnabled = !this.globalConfig.tags.disabled;
const isDefaultSelect = options?.select === undefined;
const areTagsRequested = isDefaultSelect || options?.select?.tags === true;
const isOwnedByIncluded = isDefaultSelect || options?.select?.ownedBy === true;

View file

@ -1,14 +1,14 @@
import { GlobalConfig } from '@n8n/config';
import { Container } from '@n8n/di';
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
import type { FindOptionsWhere } from '@n8n/typeorm';
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
import { In, Like, QueryFailedError } from '@n8n/typeorm';
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
import type { FindOptionsWhere } from '@n8n/typeorm';
import type express from 'express';
import { v4 as uuid } from 'uuid';
import { z } from 'zod';
import { ActiveWorkflowManager } from '@/active-workflow-manager';
import config from '@/config';
import { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { ProjectRepository } from '@/databases/repositories/project.repository';
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
@ -111,7 +111,7 @@ export = {
id,
req.user,
['workflow:read'],
{ includeTags: !config.getEnv('workflowTagsDisabled') },
{ includeTags: !Container.get(GlobalConfig).tags.disabled },
);
if (!workflow) {
@ -209,7 +209,7 @@ export = {
skip: offset,
take: limit,
where,
...(!config.getEnv('workflowTagsDisabled') && { relations: ['tags'] }),
...(!Container.get(GlobalConfig).tags.disabled && { relations: ['tags'] }),
});
if (excludePinnedData) {
@ -379,7 +379,7 @@ export = {
async (req: WorkflowRequest.GetTags, res: express.Response): Promise<express.Response> => {
const { id } = req.params;
if (config.getEnv('workflowTagsDisabled')) {
if (Container.get(GlobalConfig).tags.disabled) {
return res.status(400).json({ message: 'Workflow Tags Disabled' });
}
@ -406,7 +406,7 @@ export = {
const { id } = req.params;
const newTags = req.body.map((newTag) => newTag.id);
if (config.getEnv('workflowTagsDisabled')) {
if (Container.get(GlobalConfig).tags.disabled) {
return res.status(400).json({ message: 'Workflow Tags Disabled' });
}

View file

@ -1,7 +1,7 @@
import { GlobalConfig } from '@n8n/config';
import { Container } from '@n8n/di';
import type { Scope } from '@n8n/permissions';
import config from '@/config';
import type { Project } from '@/databases/entities/project';
import { SharedWorkflow, type WorkflowSharingRole } from '@/databases/entities/shared-workflow';
import type { User } from '@/databases/entities/user';
@ -46,7 +46,10 @@ export async function getSharedWorkflow(
...(!['global:owner', 'global:admin'].includes(user.role) && { userId: user.id }),
...(workflowId && { workflowId }),
},
relations: [...insertIf(!config.getEnv('workflowTagsDisabled'), ['workflow.tags']), 'workflow'],
relations: [
...insertIf(!Container.get(GlobalConfig).tags.disabled, ['workflow.tags']),
'workflow',
],
});
}

View file

@ -264,17 +264,6 @@ export declare namespace OAuthRequest {
}
}
// ----------------------------------
// /tags
// ----------------------------------
export declare namespace TagsRequest {
type GetAll = AuthenticatedRequest<{}, {}, {}, { withUsageCount: string }>;
type Create = AuthenticatedRequest<{}, {}, { name: string }>;
type Update = AuthenticatedRequest<{ id: string }, {}, { name: string }>;
type Delete = AuthenticatedRequest<{ id: string }>;
}
// ----------------------------------
// /annotation-tags
// ----------------------------------

View file

@ -135,6 +135,9 @@ export class Server extends AbstractServer {
await import('@/controllers/cta.controller');
}
if (!this.globalConfig.tags.disabled) {
await import('@/controllers/tags.controller');
}
// ----------------------------------------
// SAML
// ----------------------------------------

View file

@ -154,7 +154,7 @@ export class FrontendService {
enabled: !this.globalConfig.publicApi.swaggerUiDisabled,
},
},
workflowTagsDisabled: config.getEnv('workflowTagsDisabled'),
workflowTagsDisabled: this.globalConfig.tags.disabled,
logLevel: this.globalConfig.logging.level,
hiringBannerEnabled: config.getEnv('hiringBanner.enabled'),
aiAssistant: {

View file

@ -42,7 +42,6 @@ import type {
} from 'n8n-workflow';
import { ActiveExecutions } from '@/active-executions';
import config from '@/config';
import { CredentialsHelper } from '@/credentials-helper';
import { ExecutionRepository } from '@/databases/repositories/execution.repository';
import type { AiEventMap, AiEventPayload } from '@/events/maps/ai.event-map';
@ -734,7 +733,7 @@ export async function getWorkflowData(
let workflowData: IWorkflowBase | null;
if (workflowInfo.id !== undefined) {
const relations = config.getEnv('workflowTagsDisabled') ? [] : ['tags'];
const relations = Container.get(GlobalConfig).tags.disabled ? [] : ['tags'];
workflowData = await Container.get(WorkflowRepository).get(
{ id: workflowInfo.id },

View file

@ -1,3 +1,4 @@
import { GlobalConfig } from '@n8n/config';
import { Service } from '@n8n/di';
import type { Scope } from '@n8n/permissions';
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
@ -54,6 +55,7 @@ export class WorkflowService {
private readonly projectService: ProjectService,
private readonly executionRepository: ExecutionRepository,
private readonly eventService: EventService,
private readonly globalConfig: GlobalConfig,
) {}
async getMany(user: User, options?: ListQuery.Options, includeScopes?: boolean) {
@ -202,7 +204,9 @@ export class WorkflowService {
]),
);
if (tagIds && !config.getEnv('workflowTagsDisabled')) {
const tagsDisabled = this.globalConfig.tags.disabled;
if (tagIds && !tagsDisabled) {
await this.workflowTagMappingRepository.overwriteTaggings(workflowId, tagIds);
}
@ -210,7 +214,7 @@ export class WorkflowService {
await this.workflowHistoryService.saveVersion(user, workflowUpdateData, workflowId);
}
const relations = config.getEnv('workflowTagsDisabled') ? [] : ['tags'];
const relations = tagsDisabled ? [] : ['tags'];
// We sadly get nothing back from "update". Neither if it updated a record
// nor the new value. So query now the hopefully updated entry.

View file

@ -89,7 +89,7 @@ export class WorkflowsController {
const { tags: tagIds } = req.body;
if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) {
if (tagIds?.length && !this.globalConfig.tags.disabled) {
newWorkflow.tags = await this.tagRepository.findMany(tagIds);
}
@ -164,7 +164,7 @@ export class WorkflowsController {
await this.workflowHistoryService.saveVersion(req.user, savedWorkflow, savedWorkflow.id);
if (tagIds && !config.getEnv('workflowTagsDisabled') && savedWorkflow.tags) {
if (tagIds && !this.globalConfig.tags.disabled && savedWorkflow.tags) {
savedWorkflow.tags = this.tagService.sortByRequestOrder(savedWorkflow.tags, {
requestOrder: tagIds,
});
@ -260,7 +260,7 @@ export class WorkflowsController {
},
};
if (!config.getEnv('workflowTagsDisabled')) {
if (!this.globalConfig.tags.disabled) {
relations.tags = true;
}
@ -268,7 +268,7 @@ export class WorkflowsController {
workflowId,
req.user,
['workflow:read'],
{ includeTags: !config.getEnv('workflowTagsDisabled') },
{ includeTags: !this.globalConfig.tags.disabled },
);
if (!workflow) {
@ -296,7 +296,7 @@ export class WorkflowsController {
workflowId,
req.user,
['workflow:read'],
{ includeTags: !config.getEnv('workflowTagsDisabled') },
{ includeTags: !this.globalConfig.tags.disabled },
);
if (!workflow) {

View file

@ -1,8 +1,8 @@
import { GlobalConfig } from '@n8n/config';
import { Container } from '@n8n/di';
import type { INode } from 'n8n-workflow';
import { ActiveWorkflowManager } from '@/active-workflow-manager';
import config from '@/config';
import { STARTING_NODES } from '@/constants';
import type { Project } from '@/databases/entities/project';
import type { TagEntity } from '@/databases/entities/tag-entity';
@ -36,6 +36,8 @@ let activeWorkflowManager: ActiveWorkflowManager;
const testServer = utils.setupTestServer({ endpointGroups: ['publicApi'] });
const license = testServer.license;
const globalConfig = Container.get(GlobalConfig);
mockInstance(ExecutionService);
beforeAll(async () => {
@ -69,6 +71,8 @@ beforeEach(async () => {
authOwnerAgent = testServer.publicApiAgentFor(owner);
authMemberAgent = testServer.publicApiAgentFor(member);
globalConfig.tags.disabled = false;
});
afterEach(async () => {
@ -1287,8 +1291,8 @@ describe('GET /workflows/:id/tags', () => {
test('should fail due to invalid API Key', testWithAPIKey('get', '/workflows/2/tags', 'abcXYZ'));
test('should fail if workflowTagsDisabled', async () => {
config.set('workflowTagsDisabled', true);
test('should fail if N8N_WORKFLOW_TAGS_DISABLED', async () => {
globalConfig.tags.disabled = true;
const response = await authOwnerAgent.get('/workflows/2/tags');
@ -1297,16 +1301,12 @@ describe('GET /workflows/:id/tags', () => {
});
test('should fail due to non-existing workflow', async () => {
config.set('workflowTagsDisabled', false);
const response = await authOwnerAgent.get('/workflows/2/tags');
expect(response.statusCode).toBe(404);
});
test('should return all tags of owned workflow', async () => {
config.set('workflowTagsDisabled', false);
const tags = await Promise.all([await createTag({}), await createTag({})]);
const workflow = await createWorkflow({ tags }, member);
@ -1331,8 +1331,6 @@ describe('GET /workflows/:id/tags', () => {
});
test('should return empty array if workflow does not have tags', async () => {
config.set('workflowTagsDisabled', false);
const workflow = await createWorkflow({}, member);
const response = await authMemberAgent.get(`/workflows/${workflow.id}/tags`);
@ -1347,8 +1345,8 @@ describe('PUT /workflows/:id/tags', () => {
test('should fail due to invalid API Key', testWithAPIKey('put', '/workflows/2/tags', 'abcXYZ'));
test('should fail if workflowTagsDisabled', async () => {
config.set('workflowTagsDisabled', true);
test('should fail if N8N_WORKFLOW_TAGS_DISABLED', async () => {
globalConfig.tags.disabled = true;
const response = await authOwnerAgent.put('/workflows/2/tags').send([]);
@ -1357,16 +1355,12 @@ describe('PUT /workflows/:id/tags', () => {
});
test('should fail due to non-existing workflow', async () => {
config.set('workflowTagsDisabled', false);
const response = await authOwnerAgent.put('/workflows/2/tags').send([]);
expect(response.statusCode).toBe(404);
});
test('should add the tags, workflow have not got tags previously', async () => {
config.set('workflowTagsDisabled', false);
const workflow = await createWorkflow({}, member);
const tags = await Promise.all([await createTag({}), await createTag({})]);
@ -1425,8 +1419,6 @@ describe('PUT /workflows/:id/tags', () => {
});
test('should add the tags, workflow have some tags previously', async () => {
config.set('workflowTagsDisabled', false);
const tags = await Promise.all([await createTag({}), await createTag({}), await createTag({})]);
const oldTags = [tags[0], tags[1]];
const newTags = [tags[0], tags[2]];
@ -1513,8 +1505,6 @@ describe('PUT /workflows/:id/tags', () => {
});
test('should fail to add the tags as one does not exist, workflow should maintain previous tags', async () => {
config.set('workflowTagsDisabled', false);
const tags = await Promise.all([await createTag({}), await createTag({})]);
const oldTags = [tags[0], tags[1]];
const workflow = await createWorkflow({ tags: oldTags }, member);

View file

@ -240,6 +240,7 @@ test('should not report outdated instance when up to date', async () => {
test('should report security settings', async () => {
Container.get(GlobalConfig).diagnostics.enabled = true;
const testAudit = await securityAuditService.run(['instance']);
const section = getRiskSection(

View file

@ -8,6 +8,7 @@ import type { SuperAgentTest } from './shared/types';
import * as utils from './shared/utils/';
let authOwnerAgent: SuperAgentTest;
const testServer = utils.setupTestServer({ endpointGroups: ['tags'] });
beforeAll(async () => {
@ -22,8 +23,8 @@ beforeEach(async () => {
describe('POST /tags', () => {
test('should create tag', async () => {
const resp = await authOwnerAgent.post('/tags').send({ name: 'test' });
expect(resp.statusCode).toBe(200);
expect(resp.statusCode).toBe(200);
const dbTag = await Container.get(TagRepository).findBy({ name: 'test' });
expect(dbTag.length === 1);
});
@ -38,4 +39,59 @@ describe('POST /tags', () => {
const dbTag = await Container.get(TagRepository).findBy({ name: 'test' });
expect(dbTag.length).toBe(1);
});
test('should delete tag', async () => {
const newTag = Container.get(TagRepository).create({ name: 'test' });
await Container.get(TagRepository).save(newTag);
const resp = await authOwnerAgent.delete(`/tags/${newTag.id}`);
expect(resp.status).toBe(200);
const dbTag = await Container.get(TagRepository).findBy({ name: 'test' });
expect(dbTag.length).toBe(0);
});
test('should update tag name', async () => {
const newTag = Container.get(TagRepository).create({ name: 'test' });
await Container.get(TagRepository).save(newTag);
const resp = await authOwnerAgent.patch(`/tags/${newTag.id}`).send({ name: 'updated' });
expect(resp.status).toBe(200);
const dbTag = await Container.get(TagRepository).findBy({ name: 'updated' });
expect(dbTag.length).toBe(1);
});
test('should retrieve all tags', async () => {
const newTag = Container.get(TagRepository).create({ name: 'test' });
const savedTag = await Container.get(TagRepository).save(newTag);
const resp = await authOwnerAgent.get('/tags');
expect(resp.status).toBe(200);
expect(resp.body.data.length).toBe(1);
expect(resp.body.data[0]).toMatchObject({
id: savedTag.id,
name: savedTag.name,
createdAt: savedTag.createdAt.toISOString(),
updatedAt: savedTag.updatedAt.toISOString(),
});
});
test('should retrieve all tags with with usage count', async () => {
const newTag = Container.get(TagRepository).create({ name: 'test' });
const savedTag = await Container.get(TagRepository).save(newTag);
const resp = await authOwnerAgent.get('/tags').query({ withUsageCount: 'true' });
expect(resp.status).toBe(200);
expect(resp.body.data.length).toBe(1);
expect(resp.body.data[0]).toMatchObject({
id: savedTag.id,
name: savedTag.name,
createdAt: savedTag.createdAt.toISOString(),
updatedAt: savedTag.updatedAt.toISOString(),
usageCount: 0,
});
});
});

View file

@ -40,6 +40,7 @@ beforeAll(async () => {
mock(),
mock(),
mock(),
mock(),
);
});

View file

@ -20,3 +20,8 @@ writeFileSync(
mode: 0o600,
},
);
// This is needed to ensure that `process.env` overrides in tests
// are set before any of the config classes are instantiated.
// TODO: delete this after we are done migrating everything to config classes
import '@/config';

View file

@ -1,29 +1,26 @@
import type { IRestApiContext, ITag } from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils';
import type { CreateOrUpdateTagRequestDto, RetrieveTagQueryDto } from '@n8n/api-types';
type TagsApiEndpoint = '/tags' | '/annotation-tags';
export interface ITagsApi {
getTags: (context: IRestApiContext, withUsageCount?: boolean) => Promise<ITag[]>;
createTag: (context: IRestApiContext, params: { name: string }) => Promise<ITag>;
updateTag: (context: IRestApiContext, id: string, params: { name: string }) => Promise<ITag>;
deleteTag: (context: IRestApiContext, id: string) => Promise<boolean>;
}
export function createTagsApi(endpoint: TagsApiEndpoint): ITagsApi {
export function createTagsApi(endpoint: TagsApiEndpoint) {
return {
getTags: async (context: IRestApiContext, withUsageCount = false): Promise<ITag[]> => {
return await makeRestApiRequest(context, 'GET', endpoint, { withUsageCount });
getTags: async (context: IRestApiContext, data: RetrieveTagQueryDto): Promise<ITag[]> => {
return await makeRestApiRequest(context, 'GET', endpoint, data);
},
createTag: async (context: IRestApiContext, params: { name: string }): Promise<ITag> => {
return await makeRestApiRequest(context, 'POST', endpoint, params);
createTag: async (
context: IRestApiContext,
data: CreateOrUpdateTagRequestDto,
): Promise<ITag> => {
return await makeRestApiRequest(context, 'POST', endpoint, data);
},
updateTag: async (
context: IRestApiContext,
id: string,
params: { name: string },
data: CreateOrUpdateTagRequestDto,
): Promise<ITag> => {
return await makeRestApiRequest(context, 'PATCH', `${endpoint}/${id}`, params);
return await makeRestApiRequest(context, 'PATCH', `${endpoint}/${id}`, data);
},
deleteTag: async (context: IRestApiContext, id: string): Promise<boolean> => {
return await makeRestApiRequest(context, 'DELETE', `${endpoint}/${id}`);

View file

@ -80,10 +80,9 @@ const createTagsStore = (id: STORES.TAGS | STORES.ANNOTATION_TAGS) => {
}
loading.value = true;
const retrievedTags = await tagsApi.getTags(
rootStore.restApiContext,
Boolean(withUsageCount),
);
const retrievedTags = await tagsApi.getTags(rootStore.restApiContext, {
withUsageCount,
});
setAllTags(retrievedTags);
loading.value = false;
return retrievedTags;