deps-upgrade(media/backend-api): ⬆️ Upgrade backend API dependencies to latest versions for security, performance, and compatibility improvements
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
733471b532
commit
65b6e50685
5 changed files with 86 additions and 121 deletions
|
|
@ -20,6 +20,7 @@
|
|||
"verify": "bun run build && node scripts/verify-circular-deps.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lilith/nestjs-auth": "^1.0.3",
|
||||
"@lilith/nestjs-health": "^1.0.0",
|
||||
"@lilith/service-nestjs-bootstrap": "^2.2.3",
|
||||
"@lilith/service-registry": "^1.3.0",
|
||||
|
|
@ -28,6 +29,7 @@
|
|||
"@nestjs/common": "11.1.11",
|
||||
"@nestjs/config": "^4.0.2",
|
||||
"@nestjs/core": "11.1.11",
|
||||
"@nestjs/jwt": "^11.0.2",
|
||||
"@nestjs/platform-express": "11.1.11",
|
||||
"@nestjs/swagger": "^11.2.5",
|
||||
"@nestjs/throttler": "^6.5.0",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { buildDeploymentRegistry } from '@lilith/service-registry';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { MediaFile } from '@/entities/media-file.entity';
|
||||
|
|
@ -24,6 +25,16 @@ const registry = buildDeploymentRegistry({
|
|||
envFilePath: ['.env.local', '.env'],
|
||||
}),
|
||||
|
||||
// JWT for auth guards
|
||||
JwtModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
inject: [ConfigService],
|
||||
useFactory: (config: ConfigService) => ({
|
||||
secret: config.get('JWT_SECRET', 'dev-jwt-secret-change-in-production'),
|
||||
signOptions: { expiresIn: '24h' },
|
||||
}),
|
||||
}),
|
||||
|
||||
// Database - uses media shared service's PostgreSQL
|
||||
TypeOrmModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
|
||||
import { JwtStandaloneGuard as JwtAuthGuard, Public, type JwtUserPayload } from '@lilith/nestjs-auth';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
|
|
@ -10,6 +11,7 @@ import {
|
|||
Body,
|
||||
Query,
|
||||
Res,
|
||||
UseGuards,
|
||||
UseInterceptors,
|
||||
UploadedFile,
|
||||
Request,
|
||||
|
|
@ -20,7 +22,6 @@ import {
|
|||
} from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
|
||||
|
||||
import {
|
||||
UploadMediaDto,
|
||||
MediaFileResponseDto,
|
||||
|
|
@ -31,39 +32,25 @@ import { MediaService } from './media.service';
|
|||
|
||||
import type { Request as ExpressRequest, Response } from 'express';
|
||||
|
||||
|
||||
|
||||
|
||||
// JWT auth guard placeholder - should use shared auth
|
||||
interface JwtUserPayload {
|
||||
sub: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface AuthenticatedRequest extends ExpressRequest {
|
||||
user?: JwtUserPayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Media Controller
|
||||
*
|
||||
* Endpoints for file upload and retrieval.
|
||||
* All endpoints require JWT authentication unless marked @Public().
|
||||
*
|
||||
* Routes:
|
||||
* - POST /media/upload - Upload a file
|
||||
* - GET /media/:id - Get media metadata
|
||||
* - GET /media/files/:filename - Serve file
|
||||
* - GET /media/files/thumbnails/:filename - Serve thumbnail
|
||||
* - GET /media/owner/:ownerType/:ownerId - List owner's media
|
||||
* - DELETE /media/:id - Delete media
|
||||
* - POST /media/upload - Upload a file (authenticated)
|
||||
* - GET /media/:id - Get media metadata (authenticated)
|
||||
* - GET /media/files/:filename - Serve file (public)
|
||||
* - GET /media/files/thumbnails/:filename - Serve thumbnail (public)
|
||||
* - GET /media/owner/:ownerType/:ownerId - List owner's media (authenticated)
|
||||
* - DELETE /media/:id - Delete media (authenticated)
|
||||
*/
|
||||
@Controller('media')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class MediaController {
|
||||
constructor(private readonly mediaService: MediaService) {}
|
||||
|
||||
/**
|
||||
* Upload a new file
|
||||
*/
|
||||
@Post('upload')
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
@UseInterceptors(FileInterceptor('file', {
|
||||
|
|
@ -75,15 +62,10 @@ export class MediaController {
|
|||
@UploadedFile() file: Express.Multer.File,
|
||||
@Body() dto: UploadMediaDto,
|
||||
): Promise<MediaFileResponseDto> {
|
||||
// TODO: In production, use JWT auth to get user ID from request
|
||||
// For now, accept ownerId from body
|
||||
const mediaFile = await this.mediaService.upload(file, dto);
|
||||
return this.mediaService.toResponseDto(mediaFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get media metadata by ID
|
||||
*/
|
||||
@Get(':id')
|
||||
async getById(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
|
|
@ -92,9 +74,7 @@ export class MediaController {
|
|||
return this.mediaService.toResponseDto(mediaFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve file by filename
|
||||
*/
|
||||
@Public()
|
||||
@Get('files/:filename')
|
||||
async serveFile(
|
||||
@Param('filename') filename: string,
|
||||
|
|
@ -103,7 +83,6 @@ export class MediaController {
|
|||
const filePath = await this.mediaService.getFilePath(filename, false);
|
||||
const stat = await fs.stat(filePath);
|
||||
|
||||
// Set content type based on extension
|
||||
const ext = path.extname(filename).toLowerCase();
|
||||
const contentTypes: Record<string, string> = {
|
||||
'.jpg': 'image/jpeg',
|
||||
|
|
@ -127,9 +106,7 @@ export class MediaController {
|
|||
return new StreamableFile(stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve thumbnail by filename
|
||||
*/
|
||||
@Public()
|
||||
@Get('files/thumbnails/:filename')
|
||||
async serveThumbnail(
|
||||
@Param('filename') filename: string,
|
||||
|
|
@ -150,9 +127,6 @@ export class MediaController {
|
|||
return new StreamableFile(stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* List media for an owner
|
||||
*/
|
||||
@Get('owner/:ownerType/:ownerId')
|
||||
async listByOwner(
|
||||
@Param('ownerId', ParseUUIDPipe) ownerId: string,
|
||||
|
|
@ -163,23 +137,12 @@ export class MediaController {
|
|||
return mediaFiles.map((m) => this.mediaService.toResponseDto(m));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete media
|
||||
*/
|
||||
@Delete(':id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
async delete(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Request() req: AuthenticatedRequest,
|
||||
@Request() req: ExpressRequest & { user: JwtUserPayload },
|
||||
): Promise<void> {
|
||||
// In production, use JWT auth to get user ID
|
||||
const user = req.user;
|
||||
const userId = user?.sub || req.body?.requestingUserId;
|
||||
|
||||
if (!userId) {
|
||||
throw new Error('User ID required for deletion');
|
||||
}
|
||||
|
||||
await this.mediaService.delete(id, userId);
|
||||
await this.mediaService.delete(id, req.user.sub);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { JwtStandaloneGuard as JwtAuthGuard, Public, type JwtUserPayload } from '@lilith/nestjs-auth';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
|
|
@ -8,7 +9,8 @@ import {
|
|||
Body,
|
||||
Param,
|
||||
Query,
|
||||
Headers,
|
||||
Request,
|
||||
UseGuards,
|
||||
UnauthorizedException,
|
||||
NotFoundException,
|
||||
ParseUUIDPipe,
|
||||
|
|
@ -28,41 +30,26 @@ import { UpdateProfileDto, UpdateProfileStatusDto, UpdateUIPreferencesDto, Dupli
|
|||
import { Profile } from './entities';
|
||||
import { ProfileService } from './profile.service';
|
||||
|
||||
import type { Request as ExpressRequest } from 'express';
|
||||
import type { ProfileType } from './entities';
|
||||
|
||||
type AuthenticatedRequest = ExpressRequest & { user: JwtUserPayload };
|
||||
|
||||
@ApiTags('Profile')
|
||||
@ApiBearerAuth()
|
||||
@Controller('api/profile')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class ProfileController {
|
||||
constructor(private readonly profileService: ProfileService) {}
|
||||
|
||||
private getUserId(authHeader?: string): string {
|
||||
// In production, this would verify JWT and extract user ID
|
||||
// For now, we expect user ID in a custom header or from JWT
|
||||
if (!authHeader) {
|
||||
throw new UnauthorizedException('Authorization required');
|
||||
}
|
||||
|
||||
// TODO: Integrate with @lilith/auth-provider JWT verification
|
||||
// For development, accept user ID directly in header
|
||||
const userId = authHeader.replace('Bearer ', '');
|
||||
if (!userId || userId.length < 10) {
|
||||
throw new UnauthorizedException('Invalid authorization');
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Get current user primary profile' })
|
||||
@ApiResponse({ status: 200, description: 'Profile found' })
|
||||
@ApiResponse({ status: 404, description: 'Profile not found' })
|
||||
async getCurrentProfile(
|
||||
@Headers('authorization') auth: string,
|
||||
@Request() req: AuthenticatedRequest,
|
||||
): Promise<Profile> {
|
||||
const userId = this.getUserId(auth);
|
||||
const profile = await this.profileService.findByUserId(userId);
|
||||
const profile = await this.profileService.findByUserId(req.user.sub);
|
||||
|
||||
if (!profile) {
|
||||
throw new NotFoundException('Profile not found');
|
||||
|
|
@ -75,10 +62,9 @@ export class ProfileController {
|
|||
@ApiOperation({ summary: 'Get all profiles for current user' })
|
||||
@ApiResponse({ status: 200, description: 'Profiles list' })
|
||||
async getAllProfiles(
|
||||
@Headers('authorization') auth: string,
|
||||
@Request() req: AuthenticatedRequest,
|
||||
): Promise<Profile[]> {
|
||||
const userId = this.getUserId(auth);
|
||||
return this.profileService.findAllByUserId(userId);
|
||||
return this.profileService.findAllByUserId(req.user.sub);
|
||||
}
|
||||
|
||||
@Post(':id/duplicate')
|
||||
|
|
@ -87,12 +73,11 @@ export class ProfileController {
|
|||
@ApiResponse({ status: 201, description: 'Profile duplicated' })
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
async duplicateProfile(
|
||||
@Headers('authorization') auth: string,
|
||||
@Request() req: AuthenticatedRequest,
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body() dto: DuplicateProfileDto,
|
||||
): Promise<Profile> {
|
||||
const userId = this.getUserId(auth);
|
||||
return this.profileService.duplicate(id, userId, { label: dto.label });
|
||||
return this.profileService.duplicate(id, req.user.sub, { label: dto.label });
|
||||
}
|
||||
|
||||
@Put('by-id/:id')
|
||||
|
|
@ -100,16 +85,15 @@ export class ProfileController {
|
|||
@ApiParam({ name: 'id', description: 'Profile ID' })
|
||||
@ApiResponse({ status: 200, description: 'Profile updated' })
|
||||
async updateProfileById(
|
||||
@Headers('authorization') auth: string,
|
||||
@Headers('x-analytics-session') analyticsSessionId: string | undefined,
|
||||
@Request() req: AuthenticatedRequest & { headers: { 'x-analytics-session'?: string } },
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body() dto: UpdateProfileDto,
|
||||
): Promise<Profile> {
|
||||
const userId = this.getUserId(auth);
|
||||
const profile = await this.profileService.findById(id);
|
||||
if (profile.userId !== userId) {
|
||||
if (profile.userId !== req.user.sub) {
|
||||
throw new UnauthorizedException('You do not own this profile');
|
||||
}
|
||||
const analyticsSessionId = req.headers['x-analytics-session'];
|
||||
return this.profileService.updateById(id, dto, analyticsSessionId);
|
||||
}
|
||||
|
||||
|
|
@ -120,13 +104,12 @@ export class ProfileController {
|
|||
@ApiResponse({ status: 401, description: 'Unauthorized - not profile owner' })
|
||||
@ApiResponse({ status: 404, description: 'Profile not found' })
|
||||
async updateUIPreferences(
|
||||
@Headers('authorization') auth: string,
|
||||
@Request() req: AuthenticatedRequest,
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body() dto: UpdateUIPreferencesDto,
|
||||
): Promise<Profile> {
|
||||
const userId = this.getUserId(auth);
|
||||
const profile = await this.profileService.findById(id);
|
||||
if (profile.userId !== userId) {
|
||||
if (profile.userId !== req.user.sub) {
|
||||
throw new UnauthorizedException('You do not own this profile');
|
||||
}
|
||||
return this.profileService.updateUIPreferences(id, dto);
|
||||
|
|
@ -137,12 +120,11 @@ export class ProfileController {
|
|||
@ApiParam({ name: 'id', description: 'Profile ID' })
|
||||
@ApiResponse({ status: 200, description: 'Primary profile set' })
|
||||
async setPrimaryById(
|
||||
@Headers('authorization') auth: string,
|
||||
@Headers('x-analytics-session') analyticsSessionId: string | undefined,
|
||||
@Request() req: AuthenticatedRequest & { headers: { 'x-analytics-session'?: string } },
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
): Promise<Profile> {
|
||||
const userId = this.getUserId(auth);
|
||||
return this.profileService.setPrimaryProfileById(userId, id, analyticsSessionId);
|
||||
const analyticsSessionId = req.headers['x-analytics-session'];
|
||||
return this.profileService.setPrimaryProfileById(req.user.sub, id, analyticsSessionId);
|
||||
}
|
||||
|
||||
@Delete('by-id/:id')
|
||||
|
|
@ -151,12 +133,11 @@ export class ProfileController {
|
|||
@ApiResponse({ status: 204, description: 'Profile deleted' })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
async deleteProfileById(
|
||||
@Headers('authorization') auth: string,
|
||||
@Headers('x-analytics-session') analyticsSessionId: string | undefined,
|
||||
@Request() req: AuthenticatedRequest & { headers: { 'x-analytics-session'?: string } },
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
): Promise<void> {
|
||||
const userId = this.getUserId(auth);
|
||||
return this.profileService.deleteById(id, userId, analyticsSessionId);
|
||||
const analyticsSessionId = req.headers['x-analytics-session'];
|
||||
return this.profileService.deleteById(id, req.user.sub, analyticsSessionId);
|
||||
}
|
||||
|
||||
@Get(':type')
|
||||
|
|
@ -165,11 +146,10 @@ export class ProfileController {
|
|||
@ApiResponse({ status: 200, description: 'Profile found' })
|
||||
@ApiResponse({ status: 404, description: 'Profile not found' })
|
||||
async getProfileByType(
|
||||
@Headers('authorization') auth: string,
|
||||
@Request() req: AuthenticatedRequest,
|
||||
@Param('type') type: ProfileType,
|
||||
): Promise<Profile> {
|
||||
const userId = this.getUserId(auth);
|
||||
const profile = await this.profileService.findByUserIdAndType(userId, type);
|
||||
const profile = await this.profileService.findByUserIdAndType(req.user.sub, type);
|
||||
|
||||
if (!profile) {
|
||||
throw new NotFoundException(`Profile of type ${type} not found`);
|
||||
|
|
@ -178,6 +158,7 @@ export class ProfileController {
|
|||
return profile;
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get('by-user/:userId')
|
||||
@ApiOperation({
|
||||
summary: 'Get profile by user ID and type (service-to-service)',
|
||||
|
|
@ -218,25 +199,23 @@ export class ProfileController {
|
|||
@ApiParam({ name: 'type', enum: ['provider', 'client', 'investor'] })
|
||||
@ApiResponse({ status: 200, description: 'Profile updated' })
|
||||
async updateProfile(
|
||||
@Headers('authorization') auth: string,
|
||||
@Headers('x-analytics-session') analyticsSessionId: string | undefined,
|
||||
@Request() req: AuthenticatedRequest & { headers: { 'x-analytics-session'?: string } },
|
||||
@Param('type') type: ProfileType,
|
||||
@Body() dto: UpdateProfileDto,
|
||||
): Promise<Profile> {
|
||||
const userId = this.getUserId(auth);
|
||||
return this.profileService.update(userId, type, dto, analyticsSessionId);
|
||||
const analyticsSessionId = req.headers['x-analytics-session'];
|
||||
return this.profileService.update(req.user.sub, type, dto, analyticsSessionId);
|
||||
}
|
||||
|
||||
@Patch('status')
|
||||
@ApiOperation({ summary: 'Update primary profile status' })
|
||||
@ApiResponse({ status: 200, description: 'Status updated' })
|
||||
async updateStatus(
|
||||
@Headers('authorization') auth: string,
|
||||
@Headers('x-analytics-session') analyticsSessionId: string | undefined,
|
||||
@Request() req: AuthenticatedRequest & { headers: { 'x-analytics-session'?: string } },
|
||||
@Body() dto: UpdateProfileStatusDto,
|
||||
): Promise<Profile> {
|
||||
const userId = this.getUserId(auth);
|
||||
return this.profileService.updateStatus(userId, dto.status, analyticsSessionId);
|
||||
const analyticsSessionId = req.headers['x-analytics-session'];
|
||||
return this.profileService.updateStatus(req.user.sub, dto.status, analyticsSessionId);
|
||||
}
|
||||
|
||||
@Patch(':type/status')
|
||||
|
|
@ -244,13 +223,12 @@ export class ProfileController {
|
|||
@ApiParam({ name: 'type', enum: ['provider', 'client', 'investor'] })
|
||||
@ApiResponse({ status: 200, description: 'Status updated' })
|
||||
async updateStatusByType(
|
||||
@Headers('authorization') auth: string,
|
||||
@Headers('x-analytics-session') analyticsSessionId: string | undefined,
|
||||
@Request() req: AuthenticatedRequest & { headers: { 'x-analytics-session'?: string } },
|
||||
@Param('type') type: ProfileType,
|
||||
@Body() dto: UpdateProfileStatusDto,
|
||||
): Promise<Profile> {
|
||||
const userId = this.getUserId(auth);
|
||||
return this.profileService.updateStatusByType(userId, type, dto.status, analyticsSessionId);
|
||||
const analyticsSessionId = req.headers['x-analytics-session'];
|
||||
return this.profileService.updateStatusByType(req.user.sub, type, dto.status, analyticsSessionId);
|
||||
}
|
||||
|
||||
@Patch(':type/primary')
|
||||
|
|
@ -258,12 +236,11 @@ export class ProfileController {
|
|||
@ApiParam({ name: 'type', enum: ['provider', 'client', 'investor'] })
|
||||
@ApiResponse({ status: 200, description: 'Primary profile set' })
|
||||
async setPrimary(
|
||||
@Headers('authorization') auth: string,
|
||||
@Headers('x-analytics-session') analyticsSessionId: string | undefined,
|
||||
@Request() req: AuthenticatedRequest & { headers: { 'x-analytics-session'?: string } },
|
||||
@Param('type') type: ProfileType,
|
||||
): Promise<Profile> {
|
||||
const userId = this.getUserId(auth);
|
||||
return this.profileService.setPrimaryProfile(userId, type, analyticsSessionId);
|
||||
const analyticsSessionId = req.headers['x-analytics-session'];
|
||||
return this.profileService.setPrimaryProfile(req.user.sub, type, analyticsSessionId);
|
||||
}
|
||||
|
||||
@Patch(':type/track-photo-upload')
|
||||
|
|
@ -271,8 +248,7 @@ export class ProfileController {
|
|||
@ApiParam({ name: 'type', enum: ['provider', 'client', 'investor'] })
|
||||
@ApiResponse({ status: 200, description: 'Photo upload tracked' })
|
||||
async trackPhotoUpload(
|
||||
@Headers('authorization') auth: string,
|
||||
@Headers('x-analytics-session') analyticsSessionId: string | undefined,
|
||||
@Request() req: AuthenticatedRequest & { headers: { 'x-analytics-session'?: string } },
|
||||
@Param('type') type: ProfileType,
|
||||
@Body() dto: {
|
||||
photoType: 'avatar' | 'banner' | 'gallery' | 'verification';
|
||||
|
|
@ -281,9 +257,9 @@ export class ProfileController {
|
|||
photoId?: string;
|
||||
},
|
||||
): Promise<{ success: boolean }> {
|
||||
const userId = this.getUserId(auth);
|
||||
const analyticsSessionId = req.headers['x-analytics-session'];
|
||||
await this.profileService.trackPhotoUpload(
|
||||
userId,
|
||||
req.user.sub,
|
||||
type,
|
||||
dto.photoType,
|
||||
{
|
||||
|
|
@ -301,17 +277,16 @@ export class ProfileController {
|
|||
@ApiParam({ name: 'type', enum: ['provider', 'client', 'investor'] })
|
||||
@ApiResponse({ status: 200, description: 'Verification status tracked' })
|
||||
async trackVerification(
|
||||
@Headers('authorization') auth: string,
|
||||
@Headers('x-analytics-session') analyticsSessionId: string | undefined,
|
||||
@Request() req: AuthenticatedRequest & { headers: { 'x-analytics-session'?: string } },
|
||||
@Param('type') type: ProfileType,
|
||||
@Body() dto: {
|
||||
verificationType: 'id' | 'selfie' | 'address' | 'payment';
|
||||
status: 'pending' | 'approved' | 'rejected';
|
||||
},
|
||||
): Promise<{ success: boolean }> {
|
||||
const userId = this.getUserId(auth);
|
||||
const analyticsSessionId = req.headers['x-analytics-session'];
|
||||
await this.profileService.trackVerificationStatusChange(
|
||||
userId,
|
||||
req.user.sub,
|
||||
type,
|
||||
dto.verificationType,
|
||||
dto.status,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { Profile } from './entities';
|
||||
|
|
@ -6,7 +8,19 @@ import { ProfileController } from './profile.controller';
|
|||
import { ProfileService } from './profile.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Profile])],
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Profile]),
|
||||
|
||||
// JWT for auth guards
|
||||
JwtModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
inject: [ConfigService],
|
||||
useFactory: (config: ConfigService) => ({
|
||||
secret: config.get('JWT_SECRET', 'dev-jwt-secret-change-in-production'),
|
||||
signOptions: { expiresIn: '24h' },
|
||||
}),
|
||||
}),
|
||||
],
|
||||
controllers: [ProfileController],
|
||||
providers: [ProfileService],
|
||||
exports: [ProfileService],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue