mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 15:39:40 +02:00
fix(tags): add FK constraint, token validation, input validation
- Add proper FK constraint on tags.groupId -> tag_groups.id (onDelete: set null) - Validate auth token is non-empty before API requests in TagsClient - Add @IsNotEmpty/@MinLength(1) on tag and tag group name DTOs - Add @MaxLength on all query params in tag-links DTOs - Add GetTagsForEntityDto for validated query params on tags-for-entity endpoint Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
91116bf0f1
commit
11ab265d55
6 changed files with 46 additions and 8 deletions
|
|
@ -44,6 +44,10 @@ export class TagsClient {
|
|||
private async request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
||||
const token = await this.getToken();
|
||||
|
||||
if (!token) {
|
||||
throw new Error('No authentication token available');
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.authUrl}/api/v1${path}`, {
|
||||
...options,
|
||||
headers: {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
unique,
|
||||
integer,
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import { tagGroups } from './tag-groups.schema';
|
||||
|
||||
/**
|
||||
* Central tags table for all Manacore applications.
|
||||
|
|
@ -21,7 +22,7 @@ export const tags = pgTable(
|
|||
name: varchar('name', { length: 100 }).notNull(),
|
||||
color: varchar('color', { length: 7 }).default('#3B82F6'),
|
||||
icon: varchar('icon', { length: 50 }), // Optional: Phosphor Icon name
|
||||
groupId: uuid('group_id'), // Reference to tag_groups (validated in service layer)
|
||||
groupId: uuid('group_id').references(() => tagGroups.id, { onDelete: 'set null' }),
|
||||
sortOrder: integer('sort_order').default(0).notNull(),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,17 @@
|
|||
import { IsString, IsOptional, IsInt, MaxLength, Matches } from 'class-validator';
|
||||
import {
|
||||
IsString,
|
||||
IsOptional,
|
||||
IsNotEmpty,
|
||||
IsInt,
|
||||
MinLength,
|
||||
MaxLength,
|
||||
Matches,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateTagGroupDto {
|
||||
@IsString()
|
||||
@IsNotEmpty({ message: 'Group name must not be empty' })
|
||||
@MinLength(1)
|
||||
@MaxLength(100)
|
||||
name: string;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,32 @@
|
|||
import { IsString, IsOptional, IsUUID } from 'class-validator';
|
||||
import { IsString, IsOptional, IsUUID, MaxLength } from 'class-validator';
|
||||
|
||||
export class QueryTagLinksDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
appId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(255)
|
||||
entityId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
entityType?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
tagId?: string;
|
||||
}
|
||||
|
||||
export class GetTagsForEntityDto {
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
appId: string;
|
||||
|
||||
@IsString()
|
||||
@MaxLength(255)
|
||||
entityId: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import {
|
|||
BulkCreateTagLinksDto,
|
||||
SyncTagLinksDto,
|
||||
} from './dto/create-tag-link.dto';
|
||||
import { QueryTagLinksDto } from './dto/query-tag-links.dto';
|
||||
import { QueryTagLinksDto, GetTagsForEntityDto } from './dto/query-tag-links.dto';
|
||||
|
||||
@Controller('tag-links')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
|
|
@ -63,10 +63,9 @@ export class TagLinksController {
|
|||
@Get('tags-for-entity')
|
||||
async getTagsForEntity(
|
||||
@CurrentUser() user: CurrentUserData,
|
||||
@Query('appId') appId: string,
|
||||
@Query('entityId') entityId: string
|
||||
@Query() query: GetTagsForEntityDto
|
||||
) {
|
||||
return this.tagLinksService.getTagsForEntity(user.userId, appId, entityId);
|
||||
return this.tagLinksService.getTagsForEntity(user.userId, query.appId, query.entityId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,7 +1,18 @@
|
|||
import { IsString, IsOptional, IsUUID, IsInt, MaxLength, Matches } from 'class-validator';
|
||||
import {
|
||||
IsString,
|
||||
IsOptional,
|
||||
IsNotEmpty,
|
||||
IsUUID,
|
||||
IsInt,
|
||||
MinLength,
|
||||
MaxLength,
|
||||
Matches,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateTagDto {
|
||||
@IsString()
|
||||
@IsNotEmpty({ message: 'Tag name must not be empty' })
|
||||
@MinLength(1)
|
||||
@MaxLength(100)
|
||||
name: string;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue