🚀 Initialize Musadaq SaaS: Full Backend + AI + React Dashboard + Docker Setup

This commit is contained in:
Hamza-Ayed
2026-04-16 23:26:32 +03:00
commit d66891ba0f
221 changed files with 13079 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
/**
* ════════════════════════════════════════════════════════════
* مُصادَق (Musadaq) — Encryption Service
* ════════════════════════════════════════════════════════════
* يستخدم خوارزمية AES-256-GCM لتشفير البيانات الحساسة.
* يُستخدم بشكل أساسي لمفاتيح جو فوترة (API Keys).
* ════════════════════════════════════════════════════════════
*/
import { Injectable, InternalServerErrorException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as crypto from 'crypto';
@Injectable()
export class EncryptionService {
private readonly algorithm = 'aes-256-gcm';
private readonly key: Buffer;
private readonly ivLength = 16;
private readonly tagLength = 16;
constructor(private configService: ConfigService) {
const k = this.configService.getOrThrow<string>('ENCRYPTION_KEY');
this.key = Buffer.from(k, 'hex');
if (this.key.length !== 32) {
throw new Error('Encryption key must be 32 bytes (64 hex characters)');
}
}
/**
* تشفير النص
* التنسيق الناتج: iv:tag:encryptedData (hex)
*/
encrypt(text: string): string {
try {
const iv = crypto.randomBytes(this.ivLength);
const cipher = crypto.createCipheriv(this.algorithm, this.key, iv);
const encrypted = Buffer.concat([
cipher.update(text, 'utf8'),
cipher.final(),
]);
const tag = cipher.getAuthTag();
return `${iv.toString('hex')}:${tag.toString('hex')}:${encrypted.toString('hex')}`;
} catch (error) {
throw new InternalServerErrorException('Encryption failed');
}
}
/**
* فك تشفير النص
*/
decrypt(encryptedText: string): string {
try {
const parts = encryptedText.split(':');
if (parts.length !== 3) {
throw new Error('Invalid encrypted text format');
}
const iv = Buffer.from(parts[0], 'hex');
const tag = Buffer.from(parts[1], 'hex');
const encryptedData = Buffer.from(parts[2], 'hex');
const decipher = crypto.createDecipheriv(this.algorithm, this.key, iv);
decipher.setAuthTag(tag);
const decrypted = Buffer.concat([
decipher.update(encryptedData),
decipher.final(),
]);
return decrypted.toString('utf8');
} catch (error) {
throw new InternalServerErrorException('Decryption failed');
}
}
}

View File

@@ -0,0 +1,74 @@
/**
* ════════════════════════════════════════════════════════════
* مُصادَق (Musadaq) — Local Storage Service
* ════════════════════════════════════════════════════════════
*/
import { Injectable, InternalServerErrorException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as fs from 'fs';
import * as path from 'path';
@Injectable()
export class LocalStorageService {
private readonly storageRoot: string;
constructor(private configService: ConfigService) {
this.storageRoot = this.configService.get<string>('STORAGE_PATH', './uploads');
// Ensure the root storage directory exists
if (!fs.existsSync(this.storageRoot)) {
fs.mkdirSync(this.storageRoot, { recursive: true });
}
}
/**
* حفظ ملف في التخزين المحلي
*/
async saveFile(
tenantId: string,
companyId: string,
fileName: string,
buffer: Buffer,
): Promise<string> {
try {
const now = new Date();
const relativePath = path.join(
tenantId,
companyId,
'invoices',
now.getFullYear().toString(),
(now.getMonth() + 1).toString(),
);
const fullPath = path.join(this.storageRoot, relativePath);
// Create directories if they don't exist
if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath, { recursive: true });
}
const filePath = path.join(fullPath, fileName);
fs.writeFileSync(filePath, buffer);
// Return the relative path to be stored in the database
return path.join(relativePath, fileName);
} catch (error) {
throw new InternalServerErrorException('File storage failed');
}
}
/**
* حذف ملف
*/
async deleteFile(filePath: string): Promise<void> {
try {
const fullPath = path.join(this.storageRoot, filePath);
if (fs.existsSync(fullPath)) {
fs.unlinkSync(fullPath);
}
} catch (error) {
// Log error but don't fail the request
}
}
}