38dcfdfa6d
- Prisma model SearchProvider with userId/tenantId scoping
- Three default providers (Google/Bing/DuckDuckGo) as constants, always returned without DB seed
- GET/POST/DELETE search-providers endpoints on DashboardController
- Ownership verification on delete (T-05-07), default providers cannot be deleted
- CreateSearchProviderDto with class-validator: urlTemplate must contain {query} (T-05-08)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
126 lines
3.8 KiB
TypeScript
126 lines
3.8 KiB
TypeScript
import {
|
|
Body,
|
|
Controller,
|
|
Delete,
|
|
ForbiddenException,
|
|
Get,
|
|
Param,
|
|
Patch,
|
|
Post,
|
|
Put,
|
|
Req,
|
|
} from '@nestjs/common';
|
|
import { Request } from 'express';
|
|
import { DashboardService } from './dashboard.service';
|
|
import { CreateSearchProviderDto } from './dto/create-search-provider.dto';
|
|
import { CreateWidgetDto } from './dto/create-widget.dto';
|
|
import { SaveLayoutDto } from './dto/save-layout.dto';
|
|
import { UpdateWidgetConfigDto } from './dto/update-widget-config.dto';
|
|
|
|
/**
|
|
* REST controller for dashboard layout and widget instance management.
|
|
*
|
|
* All endpoints require JWT auth (global JwtAuthGuard).
|
|
* Every handler extracts userId and tenantId from the request
|
|
* and scopes all operations to the calling user (T-05-01, T-05-02).
|
|
*
|
|
* Routes:
|
|
* - GET /dashboard/layout — get user's saved layout
|
|
* - PUT /dashboard/layout — upsert user's layout
|
|
* - GET /dashboard/widgets — list user's widget instances
|
|
* - POST /dashboard/widgets — create a new widget instance
|
|
* - PATCH /dashboard/widgets/:id/config — update widget config
|
|
* - DELETE /dashboard/widgets/:id — remove a widget instance
|
|
* - GET /dashboard/search-providers — list default + user's custom providers
|
|
* - POST /dashboard/search-providers — create a custom search provider
|
|
* - DELETE /dashboard/search-providers/:id — remove a custom provider
|
|
*/
|
|
@Controller('dashboard')
|
|
export class DashboardController {
|
|
constructor(private readonly dashboardService: DashboardService) {}
|
|
|
|
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('layout')
|
|
async getLayout(@Req() req: Request) {
|
|
const { userId } = this.extractContext(req);
|
|
return this.dashboardService.getLayout(userId);
|
|
}
|
|
|
|
@Put('layout')
|
|
async saveLayout(@Req() req: Request, @Body() dto: SaveLayoutDto) {
|
|
const { userId, tenantId } = this.extractContext(req);
|
|
return this.dashboardService.saveLayout(userId, tenantId, dto);
|
|
}
|
|
|
|
@Get('widgets')
|
|
async getWidgets(@Req() req: Request) {
|
|
const { userId } = this.extractContext(req);
|
|
return this.dashboardService.getWidgets(userId);
|
|
}
|
|
|
|
@Post('widgets')
|
|
async addWidget(@Req() req: Request, @Body() dto: CreateWidgetDto) {
|
|
const { userId, tenantId } = this.extractContext(req);
|
|
return this.dashboardService.addWidget(userId, tenantId, dto);
|
|
}
|
|
|
|
@Patch('widgets/:id/config')
|
|
async updateWidgetConfig(
|
|
@Param('id') id: string,
|
|
@Req() req: Request,
|
|
@Body() dto: UpdateWidgetConfigDto,
|
|
) {
|
|
const { userId } = this.extractContext(req);
|
|
return this.dashboardService.updateWidgetConfig(id, userId, dto);
|
|
}
|
|
|
|
@Delete('widgets/:id')
|
|
async removeWidget(
|
|
@Param('id') id: string,
|
|
@Req() req: Request,
|
|
) {
|
|
const { userId } = this.extractContext(req);
|
|
return this.dashboardService.removeWidget(id, userId);
|
|
}
|
|
|
|
// --- Search Providers (05-02, D-15) ---
|
|
|
|
@Get('search-providers')
|
|
async getSearchProviders(@Req() req: Request) {
|
|
const { userId } = this.extractContext(req);
|
|
return this.dashboardService.getSearchProviders(userId);
|
|
}
|
|
|
|
@Post('search-providers')
|
|
async addSearchProvider(
|
|
@Req() req: Request,
|
|
@Body() dto: CreateSearchProviderDto,
|
|
) {
|
|
const { userId, tenantId } = this.extractContext(req);
|
|
return this.dashboardService.addSearchProvider(userId, tenantId, dto);
|
|
}
|
|
|
|
@Delete('search-providers/:id')
|
|
async removeSearchProvider(
|
|
@Param('id') id: string,
|
|
@Req() req: Request,
|
|
) {
|
|
const { userId } = this.extractContext(req);
|
|
return this.dashboardService.removeSearchProvider(id, userId);
|
|
}
|
|
}
|