second commit
Some checks failed
CI / main (push) Failing after 10s

This commit is contained in:
2026-04-07 17:52:18 +02:00
parent 23835e09b3
commit 23fb1ce7ad
12 changed files with 8834 additions and 27 deletions

View File

@@ -1,7 +1,7 @@
{
"recommendations": [
"nrwl.angular-console",
"esbenp.prettier-vscode"
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint"
]
}

View File

@@ -0,0 +1,13 @@
import { Controller, Get, Req } from '@nestjs/common';
import { Roles } from 'nest-keycloak-connect';
@Controller('shop')
export class UserController {
@Get('group')
@Roles({ roles: ['shop'] })
async getGroups(@Req() req) {
return {
user: req.user,
};
}
}

View File

@@ -0,0 +1,7 @@
export class HeaderConstants {
static readonly AUTHORIZATION = 'authorization';
static readonly TRACE_ID = 'x-traceid';
// Puoi aggiungere qui altri header futuri
static readonly CORRELATION_ID = 'x-correlation-id';
}

View File

@@ -0,0 +1,37 @@
// apps/gateway/src/common/interceptors/axios-forward.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { traceStorage } from '../storage/trace-storage';
import { HeaderConstants } from '../constants/headers.constants';
@Injectable()
export class AxiosConfigService implements OnModuleInit {
constructor(private readonly httpService: HttpService) {}
onModuleInit() {
// Accediamo all'istanza Axios interna di NestJS
const axios = this.httpService.axiosRef;
axios.interceptors.request.use((config) => {
// Recuperiamo i dati dal magazzino ALS
const store = traceStorage.getStore();
if (store) {
if (store.authorization) {
config.headers[HeaderConstants.AUTHORIZATION] = store.authorization;
}
if (store.traceId) {
config.headers[HeaderConstants.TRACE_ID] = store.traceId;
}
if (store.correlationId) {
config.headers[HeaderConstants.CORRELATION_ID] = store.correlationId;
}
}
console.log(`[Axios Outgoing] URL: ${config.url}`);
console.log(`[Axios Outgoing] Headers:`, config.headers);
return config;
});
}
}

View File

@@ -0,0 +1,39 @@
// apps/gateway/src/common/middleware/trace.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { v4 as uuidv4 } from 'uuid';
import { traceStorage } from '../storage/trace-storage';
import { HeaderConstants } from '../constants/headers.constants';
@Injectable()
export class TraceMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
// 1. Gestione Authorization: se manca, sarà undefined
const auth = req.headers[HeaderConstants.AUTHORIZATION];
if (!auth) {
console.warn(
`[Gateway] Richiesta ricevuta senza header di autorizzazione su: ${req.url}`,
);
}
// 2. Gestione TraceID: se manca, ne generiamo uno nuovo (fondamentale per il debug!)
const traceId = req.headers[HeaderConstants.TRACE_ID] || uuidv4();
const correlationId =
req.headers[HeaderConstants.CORRELATION_ID] || uuidv4();
// Opzionale: rimandiamo il traceId nella risposta per aiutare il frontend
res.setHeader(HeaderConstants.TRACE_ID, traceId);
const store = {
authorization: auth,
traceId: traceId,
correlationId: correlationId,
};
// .run() fa sì che tutto ciò che accade dentro 'next()'
// abbia accesso a questo 'store'
traceStorage.run(store, () => {
next();
});
}
}

View File

@@ -0,0 +1,11 @@
// apps/gateway/src/common/storage/trace-storage.ts
import { AsyncLocalStorage } from 'async_hooks';
export interface TraceContext {
authorization?: string;
traceId?: string;
correlationId?: string;
}
// Creiamo un'istanza globale del magazzino
export const traceStorage = new AsyncLocalStorage<TraceContext>();

39
nx.json
View File

@@ -2,15 +2,38 @@
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"analytics": false,
"namedInputs": {
"default": [
"{projectRoot}/**/*",
"sharedGlobals"
],
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default"
"default",
"!{projectRoot}/.eslintrc.json",
"!{projectRoot}/eslint.config.mjs"
],
"sharedGlobals": [
"{workspaceRoot}/.github/workflows/ci.yml"
]
"sharedGlobals": ["{workspaceRoot}/.github/workflows/ci.yml"]
},
"plugins": [
{
"plugin": "@nx/webpack/plugin",
"options": {
"buildTargetName": "build",
"serveTargetName": "serve",
"previewTargetName": "preview",
"buildDepsTargetName": "build-deps",
"watchDepsTargetName": "watch-deps",
"serveStaticTargetName": "serve-static"
}
},
{
"plugin": "@nx/eslint/plugin",
"options": {
"targetName": "lint"
}
}
],
"targetDefaults": {
"@nx/js:tsc": {
"cache": true,
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
}
}
}

View File

@@ -4,10 +4,47 @@
"license": "MIT",
"scripts": {},
"private": true,
"dependencies": {},
"devDependencies": {
"@eslint/js": "^9.8.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.0",
"@nx/eslint": "22.6.4",
"@nx/eslint-plugin": "22.6.4",
"@nx/js": "22.6.4",
"@nx/nest": "^22.6.4",
"@nx/node": "22.6.4",
"@nx/web": "22.6.4",
"@nx/webpack": "22.6.4",
"@nx/workspace": "22.6.4",
"nx": "22.6.4"
"@swc-node/register": "~1.11.1",
"@swc/core": "~1.15.5",
"@swc/helpers": "~0.5.18",
"@types/node": "20.19.9",
"@types/uuid": "^10.0.0",
"eslint": "^9.8.0",
"eslint-config-prettier": "^10.0.0",
"jsonc-eslint-parser": "^2.1.0",
"nx": "22.6.4",
"prettier": "~3.6.2",
"tslib": "^2.3.0",
"typescript": "~5.9.2",
"typescript-eslint": "^8.40.0",
"webpack-cli": "^5.1.4"
},
"dependencies": {
"@keycloak/keycloak-admin-client": "^26.5.7",
"@nestjs/axios": "^4.0.1",
"@nestjs/common": "^10.4.22",
"@nestjs/config": "^4.0.3",
"@nestjs/core": "^10.4.22",
"@nestjs/platform-express": "^10.4.22",
"axios": "^1.14.0",
"jsonwebtoken": "^9.0.3",
"jwks-rsa": "^4.0.1",
"keycloak-connect": "^26.1.1",
"nest-keycloak-connect": "^1.10.1",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.0",
"uuid": "^13.0.0"
}
}

8596
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,14 @@
#
PORT=3002
#
KEYCLOAK_URL=http://localhost:8080
# Base URL del server
KEYCLOAK_BASE_URL=http://localhost:8080
# URL Auth derivata (concatenazione)
KEYCLOAK_URL_AUTH=${KEYCLOAK_BASE_URL}/auth
# altre configurazioni
KEYCLOAK_REALM=appweb
KEYCLOAK_CLIENT_ID=gw-negozi
KEYCLOAK_CLIENT_SECRET=Nf0WIneXN9bOleTpgrbj54ypI8AnIbd5
KEYCLOAK_CLIENT_ID=user-service
KEYCLOAK_CLIENT_SECRET=tCDsN56Y3Ii4h0SfqrT4o6SoV8fw2fe8
#

View File

@@ -0,0 +1,44 @@
import KeycloakAdminClient from '@keycloak/keycloak-admin-client';
import { Injectable, OnModuleInit, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { KeycloakConfigKeys } from '@appweb/shared';
@Injectable()
export class KeycloakService implements OnModuleInit {
private kcAdminClient: KeycloakAdminClient;
private readonly logger = new Logger(KeycloakService.name);
constructor(private readonly configService: ConfigService) {}
async onModuleInit() {
// Inizializzazione del client con i valori presi dal file .env
this.kcAdminClient = new KeycloakAdminClient({
baseUrl: this.configService.get<string>(KeycloakConfigKeys.BASE_URL),
realmName: this.configService.get<string>(KeycloakConfigKeys.REALM),
});
try {
// Autenticazione tramite Service Account
await this.kcAdminClient.auth({
grantType: 'client_credentials',
clientId: this.configService.get<string>(KeycloakConfigKeys.CLIENT_ID),
clientSecret: this.configService.get<string>(KeycloakConfigKeys.CLIENT_SECRET),
});
this.logger.log(
'Connessione a Keycloak Admin API stabilita con successo.',
);
} catch (error) {
this.logger.error("Errore durante l'autenticazione su Keycloak:", error);
}
}
// Esempio: Ottieni tutti i gruppi
async getGroups() {
return await this.kcAdminClient.groups.find();
}
// Esempio: Ottieni tutti gli utenti
async getUsers() {
return await this.kcAdminClient.users.find();
}
}

View File

@@ -0,0 +1,18 @@
import { Controller, Get, Post, Body } from '@nestjs/common';
import { AuthenticatedUser, Roles } from 'nest-keycloak-connect';
@Controller('user')
export class UserController {
@Post()
@Roles({ roles: ['shop'] }) // Solo chi ha il ruolo admin può creare
async createData(@Body() data: any, @AuthenticatedUser() user: any) {
// 'user' contiene i dati del JWT (sub, preferred_username, email)
const userId = user.sub;
const userName = user.preferred_username;
console.log(`L'utente ${userName} sta creando un record`);
// Qui chiamerai il tuo service passando userId per il database
return { message: 'Dati salvati', creatoDa: userId };
}
}