ci(github): add linting and improve CI workflow
- Add ESLint configuration for backend TypeScript code - Include linting step in backend quality checks - Add linting step to GitHub Actions CI workflow - Enable configurable Prisma query logging via PRISMA_LOG_QUERIES environment variable - Update PrismaService to support dynamic log levels based on PRISMA_LOG_QUERIES - Replace BadRequestException with UnauthorizedException in receipt import security tests
This commit is contained in:
@@ -45,6 +45,10 @@ jobs:
|
|||||||
exit 1;
|
exit 1;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Lint backend
|
||||||
|
working-directory: ./backend
|
||||||
|
run: npm run lint
|
||||||
|
|
||||||
- name: Build NestJS app
|
- name: Build NestJS app
|
||||||
working-directory: ./backend
|
working-directory: ./backend
|
||||||
run: npm run build
|
run: npm run build
|
||||||
@@ -87,6 +91,10 @@ jobs:
|
|||||||
exit 1;
|
exit 1;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Lint backend
|
||||||
|
working-directory: ./backend
|
||||||
|
run: npm run lint
|
||||||
|
|
||||||
- name: Dependency audit (high+critical)
|
- name: Dependency audit (high+critical)
|
||||||
working-directory: ./backend
|
working-directory: ./backend
|
||||||
run: npm audit --audit-level=high
|
run: npm audit --audit-level=high
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import tseslint from '@typescript-eslint/eslint-plugin';
|
||||||
|
import tsParser from '@typescript-eslint/parser';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
ignores: ['dist/**', 'node_modules/**'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['src/**/*.ts'],
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsParser,
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'@typescript-eslint': tseslint,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'warn',
|
||||||
|
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
Generated
+1040
File diff suppressed because it is too large
Load Diff
@@ -11,8 +11,9 @@
|
|||||||
"prisma:migrate": "prisma migrate dev",
|
"prisma:migrate": "prisma migrate dev",
|
||||||
"prisma:deploy": "prisma migrate deploy",
|
"prisma:deploy": "prisma migrate deploy",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
|
"lint": "eslint \"src/**/*.ts\"",
|
||||||
"audit:high": "npm audit --audit-level=high",
|
"audit:high": "npm audit --audit-level=high",
|
||||||
"quality:ci": "npm run prisma:validate && npm run prisma:generate && npm run typecheck && npm test && npm run build && npm run audit:high",
|
"quality:ci": "npm run prisma:validate && npm run prisma:generate && npm run typecheck && npm run lint && npm test && npm run build && npm run audit:high",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch"
|
"test:watch": "jest --watch"
|
||||||
},
|
},
|
||||||
@@ -49,6 +50,9 @@
|
|||||||
"@types/passport-jwt": "^4.0.1",
|
"@types/passport-jwt": "^4.0.1",
|
||||||
"@types/supertest": "^7.2.0",
|
"@types/supertest": "^7.2.0",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
||||||
|
"@typescript-eslint/parser": "^8.46.2",
|
||||||
|
"eslint": "^9.38.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"supertest": "^7.2.2",
|
"supertest": "^7.2.2",
|
||||||
"ts-jest": "^29.2.6",
|
"ts-jest": "^29.2.6",
|
||||||
|
|||||||
@@ -18,8 +18,29 @@ export class PrismaService
|
|||||||
'deleteMany',
|
'deleteMany',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
private static isTruthy(value?: string): boolean {
|
||||||
|
if (!value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = value.trim().toLowerCase();
|
||||||
|
return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static resolveLogConfig(): Prisma.LogLevel[] {
|
||||||
|
const includeQueryLogs = PrismaService.isTruthy(process.env.PRISMA_LOG_QUERIES);
|
||||||
|
|
||||||
|
if (includeQueryLogs) {
|
||||||
|
return ['query', 'warn', 'error'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['warn', 'error'];
|
||||||
|
}
|
||||||
|
|
||||||
constructor(private readonly realtimeEvents: RealtimeEventsService) {
|
constructor(private readonly realtimeEvents: RealtimeEventsService) {
|
||||||
super();
|
super({
|
||||||
|
log: PrismaService.resolveLogConfig(),
|
||||||
|
});
|
||||||
|
|
||||||
const realtimeMiddleware: Prisma.Middleware = async (params, next) => {
|
const realtimeMiddleware: Prisma.Middleware = async (params, next) => {
|
||||||
const result = await next(params);
|
const result = await next(params);
|
||||||
@@ -46,6 +67,10 @@ export class PrismaService
|
|||||||
|
|
||||||
await this.$connect();
|
await this.$connect();
|
||||||
|
|
||||||
|
if (PrismaService.isTruthy(process.env.PRISMA_LOG_QUERIES)) {
|
||||||
|
this.logger.log('Prisma query logging är aktiverad (PRISMA_LOG_QUERIES=1).');
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.log('Databasanslutning etablerad.');
|
this.logger.log('Databasanslutning etablerad.');
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
||||||
import { ReceiptImportController } from './receipt-import.controller';
|
import { ReceiptImportController } from './receipt-import.controller';
|
||||||
|
|
||||||
describe('ReceiptImport controller security', () => {
|
describe('ReceiptImport controller security', () => {
|
||||||
@@ -58,7 +58,7 @@ describe('ReceiptImport controller security', () => {
|
|||||||
it('upsertUnitMapping nekar när användar-id saknas', async () => {
|
it('upsertUnitMapping nekar när användar-id saknas', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
controller.upsertUnitMapping({ productId: 1, originalUnit: 'g', preferredUnit: 'kg' } as any, { user: {} }),
|
controller.upsertUnitMapping({ productId: 1, originalUnit: 'g', preferredUnit: 'kg' } as any, { user: {} }),
|
||||||
).rejects.toThrow(BadRequestException);
|
).rejects.toThrow(UnauthorizedException);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('saveReceipt nekar global alias för icke-admin', async () => {
|
it('saveReceipt nekar global alias för icke-admin', async () => {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ services:
|
|||||||
SEED_USER2_PASSWORD: "${SEED_USER2_PASSWORD}"
|
SEED_USER2_PASSWORD: "${SEED_USER2_PASSWORD}"
|
||||||
IMPORTER_SERVICE_URL: "http://importer-api:3001"
|
IMPORTER_SERVICE_URL: "http://importer-api:3001"
|
||||||
RECEIPT_TRACE_DECISIONS: "${RECEIPT_TRACE_DECISIONS:-0}"
|
RECEIPT_TRACE_DECISIONS: "${RECEIPT_TRACE_DECISIONS:-0}"
|
||||||
|
PRISMA_LOG_QUERIES: "${PRISMA_LOG_QUERIES:-0}"
|
||||||
volumes:
|
volumes:
|
||||||
- recipe_images:/app/recipe-images
|
- recipe_images:/app/recipe-images
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
avoid_print: true
|
||||||
Reference in New Issue
Block a user