update issue filter
Gitea Actions Demo / Deploy (push) Successful in 31s

This commit is contained in:
vadim committed 2026-10-06 23:18:00 +03:00
1 parent 5607e97533
commit f44590b8ca
5 files changed
+73 -6

No files matched your search

@@ -4,7 +4,7 @@ export class ListIssuesQuery {
public readonly page: number,
public readonly userId: string,
public readonly projectId?: string,
public readonly status?: string,
public readonly status?: string[],
public readonly text?: string,
public readonly assigneeId?: string,
) {}
@@ -12,7 +12,7 @@ export interface IIssueRepository {
limit: number;
page: number;
userId: string;
status?: string;
status?: string[];
text?: string;
assigneeId?: string;
projectId?: string;
@@ -40,7 +40,7 @@ export class IssuePrismaRepository implements IIssueRepository {
limit: number;
page: number;
userId: string;
status?: string;
status?: string[];
text?: string;
assigneeId?: string;
projectId?: string;
@@ -52,7 +52,7 @@ export class IssuePrismaRepository implements IIssueRepository {
where: {
AND: [
{ OR: ownedByUser(params.userId) },
...(params.status ? [{ status: params.status }] : []),
...(params.status?.length ? [{ status: { in: params.status } }] : []),
...(params.assigneeId ? [{ assigneeId: params.assigneeId }] : []),
...(params.projectId ? [{ projectId: params.projectId }] : []),
...(params.text
@@ -0,0 +1,51 @@
import { describe, expect, it } from 'vitest';
import { plainToInstance } from 'class-transformer';
import { validateSync } from 'class-validator';
import { FilterIssueDto } from './filter-issue.dto.js';
const parse = (query: Record<string, unknown>) => {
const dto = plainToInstance(FilterIssueDto, query);
return { dto, errors: validateSync(dto) };
};
describe('FilterIssueDto.status', () => {
it('leaves status undefined when absent', () => {
const { dto, errors } = parse({});
expect(dto.status).toBeUndefined();
expect(errors).toHaveLength(0);
});
it('wraps a single status into an array', () => {
const { dto, errors } = parse({ status: 'OPEN' });
expect(dto.status).toEqual(['OPEN']);
expect(errors).toHaveLength(0);
});
it('splits a comma-separated list', () => {
const { dto, errors } = parse({ status: 'OPEN, IN_PROGRESS' });
expect(dto.status).toEqual(['OPEN', 'IN_PROGRESS']);
expect(errors).toHaveLength(0);
});
it('accepts a repeated query param', () => {
const { dto, errors } = parse({ status: ['OPEN', 'DONE'] });
expect(dto.status).toEqual(['OPEN', 'DONE']);
expect(errors).toHaveLength(0);
});
it('rejects an unknown status', () => {
const { errors } = parse({ status: 'OPEN,UNKNOWN' });
expect(errors.map((el) => el.property)).toEqual(['status']);
});
it('rejects an empty value', () => {
const { errors } = parse({ status: '' });
expect(errors.map((el) => el.property)).toEqual(['status']);
});
});
@@ -1,4 +1,7 @@
import { Transform } from 'class-transformer';
import { ArrayNotEmpty, IsArray, IsIn, IsOptional } from 'class-validator';
import { IntDecorator, StringDecorator } from '../../../../common/decorators/validators.js';
import { ISSUE_STATUSES } from '../../domain/value-objects/issue-status.vo.js';
export class FilterIssueDto {
@IntDecorator({ isOptional: true })
@@ -13,8 +16,21 @@ export class FilterIssueDto {
@StringDecorator({ isOptional: true })
assigneeId?: string;
@StringDecorator({ isOptional: true })
status?: string;
/** Comma-separated (`?status=OPEN,IN_PROGRESS`) or repeated (`?status=OPEN&status=IN_PROGRESS`) */
@IsOptional()
@Transform(({ value }) =>
value === undefined || value === null
? undefined
: ([] as unknown[])
.concat(value)
.flatMap((el) => String(el).split(','))
.map((el) => el.trim())
.filter(Boolean),
)
@IsArray()
@ArrayNotEmpty()
@IsIn(ISSUE_STATUSES.map((status) => status.alias), { each: true })
status?: string[];
@StringDecorator({ isOptional: true })
projectId?: string;