ci(github): add linting and improve CI workflow
Test Suite / backend-pr-quick (push) Has been skipped
Test Suite / quick-import-pr-quick (push) Has been skipped
Test Suite / backend-full (push) Successful in 5m14s
Test Suite / flutter-quality (push) Failing after 1m36s

- 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:
Nils-Johan Gynther
2026-05-18 23:01:29 +02:00
parent d5f903db98
commit f6ccdd859f
8 changed files with 1132 additions and 23 deletions
+22 -14
View File
@@ -37,13 +37,17 @@ jobs:
working-directory: ./backend
run: npm run prisma:generate
- name: Verify generated Prisma client is typed
working-directory: ./backend
run: |
if ! grep -q "export \* from '.prisma/client/default'" node_modules/@prisma/client/index.d.ts; then
echo "Prisma client export is unexpected";
exit 1;
fi
- name: Verify generated Prisma client is typed
working-directory: ./backend
run: |
if ! grep -q "export \* from '.prisma/client/default'" node_modules/@prisma/client/index.d.ts; then
echo "Prisma client export is unexpected";
exit 1;
fi
- name: Lint backend
working-directory: ./backend
run: npm run lint
- name: Build NestJS app
working-directory: ./backend
@@ -79,13 +83,17 @@ jobs:
working-directory: ./backend
run: npm run prisma:generate
- name: Verify generated Prisma client is typed
working-directory: ./backend
run: |
if ! grep -q "export \* from '.prisma/client/default'" node_modules/@prisma/client/index.d.ts; then
echo "Prisma client export is unexpected";
exit 1;
fi
- name: Verify generated Prisma client is typed
working-directory: ./backend
run: |
if ! grep -q "export \* from '.prisma/client/default'" node_modules/@prisma/client/index.d.ts; then
echo "Prisma client export is unexpected";
exit 1;
fi
- name: Lint backend
working-directory: ./backend
run: npm run lint
- name: Dependency audit (high+critical)
working-directory: ./backend
+26
View File
@@ -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: '^_' },
],
},
},
];
+1040
View File
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -11,8 +11,9 @@
"prisma:migrate": "prisma migrate dev",
"prisma:deploy": "prisma migrate deploy",
"typecheck": "tsc --noEmit",
"lint": "eslint \"src/**/*.ts\"",
"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:watch": "jest --watch"
},
@@ -49,6 +50,9 @@
"@types/passport-jwt": "^4.0.1",
"@types/supertest": "^7.2.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",
"supertest": "^7.2.2",
"ts-jest": "^29.2.6",
+27 -2
View File
@@ -18,8 +18,29 @@ export class PrismaService
'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) {
super();
super({
log: PrismaService.resolveLogConfig(),
});
const realtimeMiddleware: Prisma.Middleware = async (params, next) => {
const result = await next(params);
@@ -46,6 +67,10 @@ export class PrismaService
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.');
return;
} catch (error) {
@@ -71,4 +96,4 @@ export class PrismaService
async onModuleDestroy() {
await this.$disconnect();
}
}
}
@@ -1,4 +1,4 @@
import { BadRequestException } from '@nestjs/common';
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
import { ReceiptImportController } from './receipt-import.controller';
describe('ReceiptImport controller security', () => {
@@ -55,11 +55,11 @@ describe('ReceiptImport controller security', () => {
expect(receiptImportServiceMock.upsertUnitMapping).toHaveBeenCalledWith(99, 1, 'g', 'kg');
});
it('upsertUnitMapping nekar när användar-id saknas', async () => {
await expect(
controller.upsertUnitMapping({ productId: 1, originalUnit: 'g', preferredUnit: 'kg' } as any, { user: {} }),
).rejects.toThrow(BadRequestException);
});
it('upsertUnitMapping nekar när användar-id saknas', async () => {
await expect(
controller.upsertUnitMapping({ productId: 1, originalUnit: 'g', preferredUnit: 'kg' } as any, { user: {} }),
).rejects.toThrow(UnauthorizedException);
});
it('saveReceipt nekar global alias för icke-admin', async () => {
const dto = {
+1
View File
@@ -18,6 +18,7 @@ services:
SEED_USER2_PASSWORD: "${SEED_USER2_PASSWORD}"
IMPORTER_SERVICE_URL: "http://importer-api:3001"
RECEIPT_TRACE_DECISIONS: "${RECEIPT_TRACE_DECISIONS:-0}"
PRISMA_LOG_QUERIES: "${PRISMA_LOG_QUERIES:-0}"
volumes:
- recipe_images:/app/recipe-images
depends_on:
+5
View File
@@ -0,0 +1,5 @@
include: package:flutter_lints/flutter.yaml
linter:
rules:
avoid_print: true