🚀 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,14 @@
/**
* ════════════════════════════════════════════════════════════
* مُصادَق (Musadaq) — CurrentUser Decorator
* ════════════════════════════════════════════════════════════
*/
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);

View File

@@ -0,0 +1,11 @@
/**
* ════════════════════════════════════════════════════════════
* مُصادَق (Musadaq) — Roles Decorator
* ════════════════════════════════════════════════════════════
*/
import { SetMetadata } from '@nestjs/common';
import { UserRole } from '../../modules/users/enums/role.enum';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: UserRole[]) => SetMetadata(ROLES_KEY, roles);

View File

@@ -0,0 +1,54 @@
/**
* ════════════════════════════════════════════════════════════
* مُصادَق (Musadaq) — Global Exception Filter
* ════════════════════════════════════════════════════════════
*/
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
Logger,
} from '@nestjs/common';
import { Request, Response } from 'express';
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(GlobalExceptionFilter.name);
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const message =
exception instanceof HttpException
? exception.getResponse()
: 'Internal Server Error';
// Log the error
if (status >= 500) {
this.logger.error(
`${request.method} ${request.url} - ${status} - ${JSON.stringify(message)}`,
exception instanceof Error ? exception.stack : '',
);
} else {
this.logger.warn(`${request.method} ${request.url} - ${status} - ${JSON.stringify(message)}`);
}
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: typeof message === 'string' ? message : (message as any).message || message,
error: typeof message === 'string' ? null : (message as any).error || null,
});
}
}

View File

@@ -0,0 +1,30 @@
/**
* ════════════════════════════════════════════════════════════
* مُصادَق (Musadaq) — JWT Auth Guard
* ════════════════════════════════════════════════════════════
*/
import {
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Observable } from 'rxjs';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
// Add custom authentication logic here if needed
return super.canActivate(context);
}
handleRequest(err: any, user: any, info: any) {
if (err || !user) {
throw err || new UnauthorizedException('Authentication Required');
}
return user;
}
}

View File

@@ -0,0 +1,45 @@
/**
* ════════════════════════════════════════════════════════════
* مُصادَق (Musadaq) — Roles Guard
* ════════════════════════════════════════════════════════════
*/
import {
Injectable,
CanActivate,
ExecutionContext,
ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { UserRole } from '../../modules/users/enums/role.enum';
import { ROLES_KEY } from '../decorators/roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<UserRole[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
if (!user) {
return false;
}
const hasRole = requiredRoles.includes(user.role);
if (!hasRole) {
throw new ForbiddenException('Required Role Missing');
}
return true;
}
}

View File

@@ -0,0 +1,32 @@
/**
* ════════════════════════════════════════════════════════════
* مُصادَق (Musadaq) — Tenant Guard
* ════════════════════════════════════════════════════════════
* يضمن أن المستخدم الحالي يحمل نفس tenant_id الكيان المطلوب.
* يمنع الوصول العشوائي للبيانات عبر المستأجرين.
* ════════════════════════════════════════════════════════════
*/
import {
Injectable,
CanActivate,
ExecutionContext,
ForbiddenException,
} from '@nestjs/common';
@Injectable()
export class TenantGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const { user, params } = request;
if (!user || !user.tenantId) {
throw new ForbiddenException('Invalid User Context');
}
// This is a stub: real implementation will check the target entity's tenant_id
// against the user's tenant_id if applicable.
return true;
}
}

View File

@@ -0,0 +1,37 @@
/**
* ════════════════════════════════════════════════════════════
* مُصادَق (Musadaq) — Audit Log Interceptor
* ════════════════════════════════════════════════════════════
*/
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
Logger,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class AuditLogInterceptor implements NestInterceptor {
private readonly logger = new Logger(AuditLogInterceptor.name);
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const { user, method, url } = request;
return next.handle().pipe(
tap(() => {
// Only log non-GET requests (mutative operations)
if (method !== 'GET') {
this.logger.log(
`Audit: User ${user?.id || 'Anonymous'} - ${method} ${url}`,
);
// Phase 2: Save to Database
}
}),
);
}
}

View File

@@ -0,0 +1,32 @@
/**
* ════════════════════════════════════════════════════════════
* مُصادَق (Musadaq) — Tenant Middleware
* ════════════════════════════════════════════════════════════
* يستخرج tenantId من JWT ويضعه في request.
* يستخدم في تصفية البيانات (Query filtering).
* ════════════════════════════════════════════════════════════
*/
import {
Injectable,
NestMiddleware,
UnauthorizedException,
} from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class TenantMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const { user } = req as any;
if (!user || !user.tenantId) {
// Phase 1: Not always required (registration, login)
return next();
}
// Set tenant context for the request
(req as any).tenantId = user.tenantId;
next();
}
}