Skip to content

refactor(get-ahblines): Get ahblines from sqlite instead from json files #416

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -187,17 +187,12 @@ components:
direction:
type: string
example: 'inbound'
maus_version:
type: string
pattern: '^\\d+\\.\\d+\\.\\d+$'
example: '0.3.1'
pruefidentifikator:
type: string
example: '25007'
required:
- description
- direction
- maus_version
- pruefidentifikator
required:
- lines
Expand Down
1 change: 0 additions & 1 deletion src/app/core/api/models/ahb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export interface Ahb {
meta: {
'description': string;
'direction': string;
'maus_version': string;
'pruefidentifikator': string;
};
}
1 change: 0 additions & 1 deletion src/app/features/ahbs/views/ahb-page/test-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export const testData$: Observable<Ahb> = of(null).pipe(
meta: {
description: 'asd',
direction: 'LTS to abc',
maus_version: 'asdasd',
pruefidentifikator: '123',
},
lines: [
Expand Down
48 changes: 48 additions & 0 deletions src/server/entities/ahb-line.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// The here defined columns come from the ahbline table in the database.
// The table is created by the ahbline.sql file in the database/sql folder.
// Which you can find here: https://github.com/Hochfrequenz/ahb-mig-backend/releases/tag/v0.0.1

import { Entity, Column, PrimaryColumn, Index } from 'typeorm';

@Entity({ name: 'ahbline', synchronize: false })
export class AhbLine {
@PrimaryColumn({ type: 'varchar', length: 32 })
id!: string;

@Column({ type: 'integer' })
@Index('ix_ahbline_position_inside_ahb')
position_inside_ahb!: number;

@Column({ type: 'varchar', nullable: true })
segment_group_key?: string;

@Column({ type: 'varchar', nullable: true })
segment_code?: string;

@Column({ type: 'varchar', nullable: true })
data_element?: string;

@Column({ type: 'varchar', nullable: true })
segment_id?: string;

@Column({ type: 'varchar', nullable: true })
value_pool_entry?: string;

@Column({ type: 'varchar', nullable: true })
name?: string;

@Column({ type: 'varchar', nullable: true })
ahb_expression?: string;

@Column({ type: 'varchar', nullable: true })
conditions?: string;

@Column({ type: 'varchar', nullable: true })
section_name?: string;

@Column({ type: 'integer', nullable: true })
index?: number;

@Column({ type: 'varchar', length: 32, nullable: true })
ahb_id?: string;
}
3 changes: 2 additions & 1 deletion src/server/infrastructure/database.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DataSource } from 'typeorm';
import { AhbMetaInformation } from '../entities/ahb-meta-information.entity';
import { AhbLine } from '../entities/ahb-line.entity';
import path from 'path';

const dbPath = path.resolve(process.cwd(), 'src/server/data/ahb.db');
Expand All @@ -8,7 +9,7 @@ console.log('Database path:', dbPath);
export const AppDataSource = new DataSource({
type: 'sqlite',
database: dbPath,
entities: [AhbMetaInformation],
entities: [AhbMetaInformation, AhbLine],
logging: true, // Enable SQL query logging
synchronize: false, // Set to false since we already have the database schema
});
125 changes: 94 additions & 31 deletions src/server/repository/ahb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,116 @@ import { BlobServiceClient } from '@azure/storage-blob';
import { Readable } from 'stream';
import { Ahb } from '../../app/core/api/models';
import { NotFoundError } from '../infrastructure/errors';
import BlobStorageBacked from './abstract/blobStorageBacked';
import { AppDataSource } from '../infrastructure/database';
import { AhbMetaInformation } from '../entities/ahb-meta-information.entity';
import { AhbLine } from '../entities/ahb-line.entity';

export enum FileType {
CSV = 'csv',
JSON = 'json',
XLSX = 'xlsx',
}

export default class AHBRepository extends BlobStorageBacked {
private ahbContainerName: string;
export default class AHBRepository {
private blobClient?: BlobServiceClient;
private ahbContainerName?: string;

constructor(client?: BlobServiceClient) {
super(client);
if (!process.env['AHB_CONTAINER_NAME']) {
throw new Error('AHB_CONTAINER_NAME is not set');
if (client) {
this.blobClient = client;
this.ahbContainerName = process.env['AHB_CONTAINER_NAME'];
}
this.ahbContainerName = process.env['AHB_CONTAINER_NAME'];
}

// Retrieve a single AHB from the blob storage
// 1. Get the container client (all AHB blobs are stored in the same container)
// 2. Get the blob name based on the pruefi, formatVersion and file type
// 3. Get the block blob client
// 4. Download the blob
// 5. Convert the blob content to a string
// 6. Parse the string to an Ahb object
// Retrieve a single AHB from either database (JSON) or blob storage (XLSX/CSV)
public async get(pruefi: string, formatVersion: string, type: FileType): Promise<Ahb | Buffer> {
const containerClient = this.client.getContainerClient(this.ahbContainerName);
const blobName = await this.getBlobName(pruefi, formatVersion, type);
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
const downloadBlockBlobResponse = await blockBlobClient.download(0);
if (type === FileType.JSON) {
return this.getFromDatabase(pruefi, formatVersion);
} else {
return this.getFromBlobStorage(pruefi, formatVersion, type);
}
Comment on lines +28 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

das ist aber langfristig schwer zu maintainen, oder? vermute mal, das wird in einem folge-pr geändert.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wollte das feature mit den excel-files noch weiter anbieten.
Wo siehst du Bedenken?

}

const content = await this.streamToBuffer(
downloadBlockBlobResponse.readableStreamBody as Readable
);
private async getFromDatabase(pruefi: string, formatVersion: string): Promise<Ahb> {
// Initialize the database connection if not already initialized
if (!AppDataSource.isInitialized) {
await AppDataSource.initialize();
}

if (type === FileType.JSON) {
const jsonString = content.toString('utf-8');
return JSON.parse(jsonString) as Ahb;
// Get the meta information and lines from the database
const metaInfo = await AppDataSource.getRepository(AhbMetaInformation)
.createQueryBuilder('ami')
.where('ami.edifact_format_version = :formatVersion', { formatVersion })
.andWhere('ami.pruefidentifikator = :pruefi', { pruefi })
.getOne();

if (!metaInfo) {
throw new NotFoundError(
`AHB not found for pruefi ${pruefi} and format version ${formatVersion}`
);
}

const lines = await AppDataSource.getRepository(AhbLine)
.createQueryBuilder('al')
.where('al.ahb_id = :ahbId', { ahbId: metaInfo.ahb_id })
.orderBy('al.position_inside_ahb', 'ASC')
.getMany();

// Transform the data to match the API schema
return {
meta: {
description: metaInfo.description || '',
direction: metaInfo.direction || '',
pruefidentifikator: metaInfo.pruefidentifikator,
},
lines: lines.map(line => ({
ahb_expression: line.ahb_expression || '',
conditions: line.conditions || '',
data_element: line.data_element || '',
guid: line.id,
index: line.index || 0,
name: line.name || '',
section_name: line.section_name || '',
segment_code: line.segment_code || '',
segment_group_key: line.segment_group_key || '',
value_pool_entry: line.value_pool_entry || '',
})),
};
}

private async getFromBlobStorage(
pruefi: string,
formatVersion: string,
type: FileType
): Promise<Buffer> {
if (!this.blobClient || !this.ahbContainerName) {
// Initialize blob client if not already done
if (!process.env['AZURE_BLOB_STORAGE_CONNECTION_STRING']) {
throw new NotFoundError(
'Azure Blob Storage connection string is not configured (AZURE_BLOB_STORAGE_CONNECTION_STRING)'
);
}
if (!process.env['AHB_CONTAINER_NAME']) {
throw new NotFoundError('AHB container name is not configured (AHB_CONTAINER_NAME)');
}
this.blobClient = BlobServiceClient.fromConnectionString(
process.env['AZURE_BLOB_STORAGE_CONNECTION_STRING']
);
this.ahbContainerName = process.env['AHB_CONTAINER_NAME'];
}

return content;
const containerClient = this.blobClient.getContainerClient(this.ahbContainerName);
const blobName = await this.getBlobName(pruefi, formatVersion, type);
const blockBlobClient = containerClient.getBlockBlobClient(blobName);

try {
const downloadBlockBlobResponse = await blockBlobClient.download(0);
return await this.streamToBuffer(downloadBlockBlobResponse.readableStreamBody as Readable);
} catch (error) {
throw new NotFoundError(
`File not found for pruefi ${pruefi} and format version ${formatVersion}`
);
}
}

private async streamToBuffer(readableStream: Readable): Promise<Buffer> {
Expand All @@ -58,9 +127,6 @@ export default class AHBRepository extends BlobStorageBacked {
});
}

// Get the blob name based on the pruefi, formatVersion and file type.
// Structure of a blob name: {formatVersion}/{format}/{fileTypeDirectory}/{pruefi}.{fileType}
// This method needs to further to find {format} and {fileTypeDirectory}.
private async getBlobName(
pruefi: string,
formatVersion: string,
Expand All @@ -71,7 +137,6 @@ export default class AHBRepository extends BlobStorageBacked {
return `${formatVersion}/${format}/${fileFormatDirectoryName}/${pruefi}.${type.toString()}`;
}

// Get the directory name for a specified file type
private getFileTypeDirectoryName(fileType: FileType): string {
switch (fileType) {
case FileType.CSV:
Expand All @@ -85,8 +150,6 @@ export default class AHBRepository extends BlobStorageBacked {
}
}

// Retrieve the format name by looking at the blobs names in the container.
// Assuming each pruefi is unique for a formatVersion.
private getFormatName(pruefi: string): string {
const mapping: { [key: string]: string } = {
'99': 'APERAK',
Expand Down