feat: поле tag хоста из строки в массив tags#159
Conversation
Merge pull request remnawave#155 from remnawave/dev
Миграция tag: string | null → tags: string[] по аналогии с нодами.
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
PR Summary
|
Greptile SummaryThis PR migrates the host The implementation is thorough and consistent across all layers. The SQL migration correctly preserves existing data by wrapping the old Confidence Score: 5/5Safe to merge — migration preserves data, all layers updated consistently, no logic errors. No P0 or P1 findings. The two P2 comments are a redundant NOT NULL guard in the Kysely query and an unrelated placeholder SECURITY.md file — neither affects correctness or runtime behaviour. No files require special attention beyond the two minor P2 notes.
|
| Filename | Overview |
|---|---|
| prisma/migrations/20260408120000_migrate_host_tag_to_tags/migration.sql | Adds tags TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[], migrates existing tag values to 1-element arrays, then drops the old column. Data preservation is correct. |
| prisma/schema.prisma | Updates Hosts model: tag String? → tags String[] @default([]). Consistent with the migration and mirrors the Nodes model pattern. |
| libs/contract/commands/hosts/create.command.ts | Replaces tag: z.optional(z.string()...) with tags: z.optional(z.array(...).max(10).default([])). Validation is complete with regex, per-tag length, and array-size limits. |
| libs/contract/commands/hosts/update.command.ts | Same array validation as create, intentionally without .default([]) so omitting tags in a PATCH leaves existing tags unchanged — correct PATCH semantics. |
| src/modules/hosts/repositories/hosts.repository.ts | Rewrites getAllHostTags() to use unnest(tags) via Kysely (matching the nodes pattern). Excludes tags from findByCriteria() Omit type to avoid Prisma array-filter limitations. Correct and clean. |
| src/modules/subscription-template/generators/xray-json.generator.service.ts | Updates three tag-based behaviours: useHostTagAsTag takes tags[0], sameTagAsRecipient checks for array intersection, tagRegex tests each tag. All correctly adapted to the array model. |
| src/modules/subscription-template/resolve-proxy/resolve-proxy-config.service.ts | Maps inputHost.tags instead of inputHost.tag into metadata; stub object now initialises with tags: []. No issues. |
| src/modules/subscription-template/resolve-proxy/interfaces/resolved-proxy-config.interface.ts | Interface updated: `tag: string |
| src/modules/hosts/entities/hosts.entity.ts | Entity field updated to tags: string[]. No issues. |
| src/modules/hosts/hosts.converter.ts | Converter updated to map tags instead of tag. Clean 1-line change. |
| src/modules/hosts/models/host.response.model.ts | Response model updated to expose tags: string[]. No issues. |
| libs/contract/models/resolved-proxy-config.schema.ts | Schema updated: tag: z.string().nullable() → tags: z.array(z.string()).default([]). Consistent with interface and rest of changes. |
| SECURITY.md | Adds a SECURITY.md file with GitHub's default placeholder text — unrelated to the host tags migration. |
Sequence Diagram
sequenceDiagram
participant Client
participant Contract as Zod Contract
participant Service as HostsService
participant Repo as HostsRepository
participant DB as PostgreSQL
Client->>Contract: POST /api/hosts { tags: [TAG1, TAG2] }
Contract->>Contract: validate array(regex, max 32), max 10 items
Contract->>Service: CreateHostCommand.Request
Service->>Repo: create(entity)
Repo->>DB: INSERT INTO hosts (tags, ...) VALUES (ARRAY['TAG1','TAG2'], ...)
Client->>Contract: GET /api/hosts
Contract-->>Client: HostsSchema { tags: string[] }
Service->>Repo: getAllHostTags()
Repo->>DB: SELECT DISTINCT unnest(tags) AS tag FROM hosts ORDER BY tag
DB-->>Repo: flat list of unique tags
Repo-->>Service: string[]
Reviews (1): Last reviewed commit: "fix: имя миграции по конвенции 14-значно..." | Re-trigger Greptile
| .selectFrom('hosts') | ||
| .select(sql<string>`unnest(tags)`.as('tag')) | ||
| .distinct() | ||
| .where('tags', 'is not', null) |
There was a problem hiding this comment.
Redundant null-check on a NOT NULL column
tags is declared TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[] in the migration, so this WHERE tags IS NOT NULL predicate will always be true and adds no filtering. unnest(ARRAY[]::TEXT[]) already returns zero rows for empty arrays, so there is no functional regression from removing it — but it may mislead future readers into thinking null values are possible.
Or simply omit the .where(...) clause entirely, matching the actual semantics.
| # Security Policy | ||
|
|
||
| ## Reporting a Vulnerability | ||
|
|
||
| Use this section to tell people how to report a vulnerability. | ||
|
|
||
| Tell them where to go, how often they can expect to get an update on a | ||
| reported vulnerability, what to expect if the vulnerability is accepted or | ||
| declined, etc. |
There was a problem hiding this comment.
Placeholder SECURITY.md included in feature PR
This file contains GitHub's default template text ("Tell them where to go, how often they can expect to get an update...") and is unrelated to the host-tags migration. It should either be filled in with a real vulnerability-disclosure policy or removed from this PR and tracked separately.
|
Спасибо. В планах смержить ближе к релизу 2.9.0. |
Описание
Миграция поля хоста
tagиз одиночной строки (tag: string | null) в массив (tags: string[]) — по аналогии с тем, как теги уже реализованы для нод.Мотивация
Сейчас хост поддерживает только один тег, в то время как ноды уже поддерживают несколько. Это ограничивает возможности маркировки — например, нельзя пометить хост сразу для нескольких сценариев использования (тип канала, регион, назначение и т.д.).
Что изменено
База данных:
tag String?→tags String[] @default([])tags, переносит данные изtag, удаляет старую колонкуКонтракт (libs/contract):
HostsSchema:tag: z.string().nullable()→tags: z.array(z.string()).default([])CreateHostCommand/UpdateHostCommand: валидация массива с regex на каждый элемент, максимум 10 теговProxyEntryMetadataSchema:tag→tagsБэкенд:
HostsEntity,HostResponseModel,HostsConverter: обновление типа поляHostsRepository.getAllHostTags(): переписан наunnest(tags)(как в нодах)HostsRepository.findByCriteria(): исключёнtagsиз Prisma where-фильтраresolve-proxy-config.service.ts: маппинг metadatatag→tagsxray-json.generator.service.ts:useHostTagAsTag: берёт первый тег как outbound tagsameTagAsRecipient: проверяет пересечение массивовtagRegex: матчит если хотя бы один тег подходит под regexЛомающие изменения
Изменён API-контракт во всех ручках хостов (
POST /api/hosts,PATCH /api/hosts,GET /api/hosts): полеtag: string | nullзаменено наtags: string[]. Фронтенд обновлён в remnawave/frontend#341.