Skip to content

Code-Konventionen

TCM365 erzwingt konsistente Codierungsstandards in der gesamten Codebasis. Dieser Guide behandelt TypeScript/Backend-Konventionen, Vue/Frontend-Konventionen, Datenbank-Patterns und das verpflichtende Commit-Nachrichtenformat.


TypeScript / Backend Konventionen

Code Style

TCM365 verwendet ESLint und Prettier mit folgender Konfiguration:

Einstellung Wert
singleQuote true
trailingComma all
printWidth 100
tabWidth 2
semi true

Formatierung und Linting ausführen:

cd backend-js
npm run format    # Prettier
npm run lint      # ESLint

TypeScript Strict Mode

Das Backend verwendet striktes TypeScript mit folgenden Compiler-Optionen:

  • noImplicitAny -- Keine impliziten any Typen
  • strictNullChecks -- Null Safety Enforcement
  • strict -- Alle strikten Checks aktiviert

Keine any Typen

Die Verwendung von any ist in der gesamten Codebasis verboten. Verwenden Sie stattdessen spezifische Typen, Generics oder unknown mit Type Guards.

Namenskonventionen

Element Konvention Beispiel
Entities PascalCase M365Tenant, VendorTenant
Services PascalCase SnapshotService, GraphService
Controllers PascalCase SnapshotsController
Interfaces PascalCase VendorAdapter
DTOs PascalCase CreateSnapshotDto
Methoden camelCase createSnapshot(), findById()
Variablen camelCase tenantConfig, snapshotData
Konstanten UPPER_SNAKE UTCM_LIMITS, ALL_GRAPH_ENDPOINTS
DB Spalten snake_case created_at, vendor_tenant_id
DB Tabellen snake_case vendor_tenants, audit_logs
Routen kebab-case /configuration-drift, /ca-whatif
Dateien kebab-case vendor-adapter.interface.ts

NestJS Modul-Pattern

Jedes Feature-Modul folgt dem Standard NestJS Modul-Pattern:

modules/{feature}/
  ├── {feature}.module.ts       # Modul-Definition
  ├── {feature}.controller.ts   # HTTP Endpoints
  ├── {feature}.service.ts      # Business-Logik
  ├── {feature}.service.spec.ts # Unit Tests
  └── dto/                      # Data Transfer Objects
      ├── create-{feature}.dto.ts
      └── update-{feature}.dto.ts

Modul-Definition:

@Module({
  imports: [TypeOrmModule.forFeature([MyEntity])],
  controllers: [MyController],
  providers: [MyService],
  exports: [MyService],
})
export class MyModule {}

Service Patterns

  • Dependency Injection verwenden -- Services niemals manuell instanziieren
  • Immer async/await -- Alle I/O Operationen müssen asynchron sein
  • Config via ConfigService -- Niemals process.env direkt verwenden
  • TypeORM Repositories verwenden -- Keine rohen SQL Queries schreiben
@Injectable()
export class MyService {
  constructor(
    @InjectRepository(MyEntity)
    private readonly myRepo: Repository<MyEntity>,
    private readonly configService: ConfigService,
  ) {}

  async findAll(): Promise<MyEntity[]> {
    return this.myRepo.find();
  }
}

DTO Validation

Alle DTOs verwenden class-validator Decorators für Input-Validierung:

import { IsString, IsNotEmpty, IsOptional, IsUUID } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';

export class CreateExampleDto {
  @ApiProperty({ description: 'Name des Beispiels' })
  @IsString()
  @IsNotEmpty()
  name: string;

  @ApiPropertyOptional({ description: 'Optionale Beschreibung' })
  @IsString()
  @IsOptional()
  description?: string;

  @ApiProperty({ description: 'Vendor Tenant ID' })
  @IsUUID()
  vendorTenantId: string;
}

Path Aliases

Verwenden Sie Path Aliases anstelle von tiefen relativen Imports:

// Bevorzugt:
import { User } from '@entities/user.entity';
import { JwtAuthGuard } from '@common/guards/jwt-auth.guard';

// Vermeiden:
import { User } from '../../../entities/user.entity';

Verfügbare Aliases: @/*, @config/*, @database/*, @entities/*, @modules/*, @common/*.

Guard und Decorator Verwendung

@Controller('my-feature')
@ApiTags('My Feature')
@UseGuards(JwtAuthGuard, RolesGuard)
export class MyController {
  @Get()
  @Roles(UserRole.VIEWER)
  @ApiOperation({ summary: 'Alle Einträge auflisten' })
  async findAll(@CurrentUser() user: User) {
    // ...
  }

  @Post()
  @Roles(UserRole.TENANT_ADMIN)
  @Permissions('my-feature:write')
  @ApiOperation({ summary: 'Neuen Eintrag erstellen' })
  async create(@Body() dto: CreateDto, @CurrentUser() user: User) {
    // ...
  }
}

Vue / Frontend Konventionen

Komponenten-Benennung

Element Konvention Beispiel
Komponenten PascalCase TenantCard.vue, DiffViewer.vue
Views PascalCase SnapshotsView.vue
Nicht-Komponenten kebab-case snapshot-helpers.ts
Composables camelCase useAsyncAction.ts
Services camelCase snapshotService.ts

Composition API

TCM365 verwendet die Vue 3 Composition API mit <script setup>:

<script setup>
import { ref, computed, onMounted } from 'vue';
import { useAuthStore } from '@/stores/auth';
import { snapshotService } from '@/services/snapshotService';

const authStore = useAuthStore();
const snapshots = ref([]);
const loading = ref(false);

const filteredSnapshots = computed(() =>
  snapshots.value.filter(s => s.status === 'completed')
);

onMounted(async () => {
  loading.value = true;
  try {
    snapshots.value = await snapshotService.getAll();
  } finally {
    loading.value = false;
  }
});
</script>

Tailwind CSS

Alle Styles verwenden Tailwind CSS Utility-Klassen. Vermeiden Sie eigene CSS-Dateien, es sei denn es ist zwingend erforderlich.

<template>
  <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
    <h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
      {{ title }}
    </h2>
    <p class="text-sm text-gray-600 dark:text-gray-400">
      {{ description }}
    </p>
  </div>
</template>

State Management

Verwenden Sie Pinia Stores in frontend/src/stores/ für geteilten State. Halten Sie komponentenlokalen State in ref() und reactive().

API Aufrufe

Alle API Aufrufe erfolgen über Service-Dateien in frontend/src/services/:

// services/snapshotService.ts
import api from './api';

export const snapshotService = {
  getAll: () => api.get('/snapshots'),
  getById: (id: string) => api.get(`/snapshots/${id}`),
  create: (data: any) => api.post('/snapshots', data),
  bulkDelete: (ids: string[]) => api.post('/snapshots/bulk-delete', { ids }),
};

Datenbank-Konventionen

Primary Keys

Alle Entities verwenden UUID Primary Keys (keine Auto-Increment Integer):

@Entity('examples')
export class Example extends BaseEntity {
  // id (UUID), created_at, updated_at geerbt von BaseEntity
}

Timestamps

Die meisten Tabellen enthalten created_at und updated_at Spalten, geerbt von BaseEntity.

JSONB Spalten

Verwenden Sie PostgreSQL jsonb für flexible Metadaten-Speicherung:

@Column({ type: 'jsonb', nullable: true })
metadata: Record<string, any>;

Migrationen

  • Alle Schema-Änderungen erfordern TypeORM Migrationen in backend-js/src/database/migrations/
  • Migrationen müssen deterministisch und reversibel sein
  • Tenant-bezogene Tabellen müssen sowohl im public als auch in bestehenden tenant_{uuid} Schemas erstellt werden
  • Bestehende Migrationsdateien niemals ändern -- immer neue Migrationen erstellen

Row-Level Security

Die Datenbank verwendet duale PostgreSQL Rollen:

Rolle Zweck
tcm_admin Migrations-Rolle mit BYPASSRLS
tcm_app Laufzeit-Rolle, die RLS Policies respektiert

Die Applikation setzt app.current_org_id auf jeder Datenbankverbindung für automatische Zeilenfilterung.

Single Table Inheritance (STI)

Vendor Tenants verwenden STI mit einer vendor Discriminator-Spalte:

// Basis-Entity
@Entity('vendor_tenants')
@TableInheritance({ column: { type: 'varchar', name: 'vendor' } })
export class VendorTenant extends BaseEntity { ... }

// Kind-Entity
@ChildEntity('microsoft')
export class M365VendorTenant extends VendorTenant { ... }

// Kind-Entity
@ChildEntity('zscaler')
export class ZscalerVendorTenant extends VendorTenant { ... }

Neue Vendors

Neue Vendor-Integrationen erstellen einen neuen @ChildEntity der VendorTenant erweitert -- keine separate Tabelle. Dies stellt konsistente Abfragen und Beziehungen über alle Vendors hinweg sicher.


Conventional Commits (Pflicht)

Alle Commits müssen der Conventional Commits Spezifikation folgen.

Format

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

Commit Types

Type SemVer Auswirkung Wann verwenden
feat MINOR Bump Neues Feature oder Funktionalität
fix PATCH Bump Bugfix
feat! MAJOR Bump Breaking Change (haenge ! nach Typ an)
fix! MAJOR Bump Breaking Bugfix
docs keine Nur Dokumentationsaenderungen
test keine Tests hinzufügen oder aktualisieren
refactor keine Code-Änderung ohne Verhaltensänderung
perf keine Performance-Verbesserung
chore keine Dependencies, Config, Tooling

Scopes (optional, aber empfohlen)

Scope-Format Beispiel
Modulnamen fix(snapshots): correct bulk delete query
Bereichsnamen chore(deps): update NestJS to 10.3
Jira Referenzen fix(anomaly): DTO validation TCM-14

Beispiele

# Feature
feat(snapshots): add bulk delete endpoint

# Bugfix
fix(drift): correct drift severity calculation for Zscaler

# Breaking Change
feat!: redesign tenant API response format

BREAKING CHANGE: Tenant API responses now use camelCase instead of snake_case.

# Dokumentation
docs(api): update endpoint reference for v2.5.0

# Test
test(baselines): add compliance engine unit tests

# Chore
chore(deps): upgrade TypeORM to 0.3.20

Code Review Checkliste

Vor der Code-Abgabe zur Review sicherstellen:

  • Code folgt Namenskonventionen (PascalCase Entities, camelCase Methoden, snake_case DB)
  • DTOs haben class-validator Decorators
  • Controller haben Swagger Decorators (@ApiOperation, @ApiResponse, @ApiTags)
  • Guards sind korrekt angewendet (@UseGuards, @Roles, @Permissions)
  • Alle I/O Operationen verwenden async/await
  • Config wird via ConfigService abgerufen, nicht process.env
  • Tests geschrieben und bestanden
  • Code formatiert mit npm run format
  • Keine Lint-Fehler von npm run lint
  • Commit-Nachricht folgt Conventional Commits
  • Neue Entities haben zugehörige Migrationen
  • Tenant-bezogene Entities aktualisieren sowohl Public als auch Tenant Schemas