eebceb298d
- CR-01: fix SSRF bypass — isPrivateIpv6 now delegates ::ffff:<ipv4> to
isPrivateIpv4, covering 172.16-31.x and 169.254.x ranges
- CR-02: add ParseUUIDPipe to GET /favorites widgetId param + service guard
so missing widgetId returns 400 instead of leaking all user favorites
- WR-01: link-widget — replace raw 'link.error' key with t('link.error') (4 sites)
- WR-02: favorites-widget — fix load-path error to use t('favorites.error')
- WR-03: widget-catalog-modal — move aria-hidden from outer wrapper to backdrop
- WR-04: calculator — remove duplicate M button (MR clone); MC/MR/M+/M−/MS remain
- WR-05: schema — add FavoriteLink→WidgetInstance FK with onDelete:Cascade
- IN-01: create-widget.dto.ts — update comment from four to eight supported types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
90 lines
2.2 KiB
TypeScript
90 lines
2.2 KiB
TypeScript
import {
|
|
Body,
|
|
Controller,
|
|
Delete,
|
|
ForbiddenException,
|
|
Get,
|
|
Param,
|
|
ParseUUIDPipe,
|
|
Patch,
|
|
Post,
|
|
Query,
|
|
Req,
|
|
} from '@nestjs/common';
|
|
import { Request } from 'express';
|
|
import { CreateFavoriteDto } from './dto/create-favorite.dto';
|
|
import { UpdateFavoriteDto } from './dto/update-favorite.dto';
|
|
import { FavoritesService } from './favorites.service';
|
|
|
|
/**
|
|
* REST controller for per-user, per-widget favorite links.
|
|
*
|
|
* All routes are protected by the global JwtAuthGuard + TenantGuard.
|
|
*
|
|
* Routes:
|
|
* - GET /favorites?widgetId= — list favorites for a widget instance
|
|
* - POST /favorites — create a favorite (triggers server-side icon discovery)
|
|
* - PATCH /favorites/:id — update a favorite (ownership verified in service)
|
|
* - DELETE /favorites/:id — delete a favorite (ownership verified in service)
|
|
*/
|
|
@Controller('favorites')
|
|
export class FavoritesController {
|
|
constructor(private readonly favoritesService: FavoritesService) {}
|
|
|
|
private extractContext(req: Request) {
|
|
const userId = (req as any).user?.id;
|
|
const tenantId =
|
|
(req as any).tenantId ?? (req as any).user?.tenantId;
|
|
|
|
if (!tenantId) {
|
|
throw new ForbiddenException('No tenant context');
|
|
}
|
|
if (!userId) {
|
|
throw new ForbiddenException('No user context');
|
|
}
|
|
|
|
return { userId, tenantId };
|
|
}
|
|
|
|
@Get()
|
|
async list(
|
|
@Query('widgetId', ParseUUIDPipe) widgetId: string,
|
|
@Req() req: Request,
|
|
) {
|
|
const { userId } = this.extractContext(req);
|
|
|
|
return this.favoritesService.list(userId, widgetId);
|
|
}
|
|
|
|
@Post()
|
|
async create(
|
|
@Body() dto: CreateFavoriteDto,
|
|
@Req() req: Request,
|
|
) {
|
|
const { userId, tenantId } = this.extractContext(req);
|
|
|
|
return this.favoritesService.create(userId, tenantId, dto);
|
|
}
|
|
|
|
@Patch(':id')
|
|
async update(
|
|
@Param('id') id: string,
|
|
@Body() dto: UpdateFavoriteDto,
|
|
@Req() req: Request,
|
|
) {
|
|
const { userId } = this.extractContext(req);
|
|
|
|
return this.favoritesService.update(id, userId, dto);
|
|
}
|
|
|
|
@Delete(':id')
|
|
async remove(
|
|
@Param('id') id: string,
|
|
@Req() req: Request,
|
|
) {
|
|
const { userId } = this.extractContext(req);
|
|
|
|
return this.favoritesService.remove(id, userId);
|
|
}
|
|
}
|