P1: drivers + trips(★state machine) + matching(Redis GEO) + tariff engine + maps + realtime + seed

- drivers: apply/approve/status/location (+Redis GEO presence)
- trips: request→accept→status transitions + trip_events audit + offers via socket
- matching: Redis GEO nearby drivers
- tariff: JSON-rule engine (time_or_distance/window/rounding) + quote endpoint
- maps: straight-line routing seam (انطلق later)
- realtime: Socket.IO gateway (JWT auth, tenant rooms)
- seed: demo tenant siro + default tariff on boot
- migration InitP1 (drivers/tariffs/trips/trip_events)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 16:41:58 +03:00
co-authored by Claude Opus 4.8
parent 4397b07d2f
commit 00870a8cc3
26 changed files with 1358 additions and 1 deletions
+57
View File
@@ -0,0 +1,57 @@
import {
OnGatewayConnection,
WebSocketGateway,
WebSocketServer,
} from '@nestjs/websockets';
import { Logger } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Server, Socket } from 'socket.io';
/**
* البوابة الحية (Socket.IO) — docs/09. المصادقة على الاتصال بـ JWT،
* والانضمام لغرف مُنطَّقة بالمستأجر (لا تسريب بين المستأجرين).
* غرف: tenant:{id}:driver:{driverId} · tenant:{id}:trip:{tripId} · tenant:{id}:dispatch
*/
@WebSocketGateway({ cors: true })
export class RealtimeGateway implements OnGatewayConnection {
private readonly logger = new Logger('Realtime');
@WebSocketServer()
server: Server;
constructor(private readonly jwt: JwtService) {}
handleConnection(client: Socket) {
try {
const token =
(client.handshake.auth?.token as string) ||
(client.handshake.headers?.authorization as string)?.replace('Bearer ', '');
if (!token) throw new Error('no token');
const p: any = this.jwt.verify(token);
const tenantId = p.tenant_id;
client.data.user = { userId: p.sub, tenantId, role: p.role };
client.join(`tenant:${tenantId}:user:${p.sub}`);
if (p.role === 'driver') client.join(`tenant:${tenantId}:drivers`);
this.logger.debug(`connected user=${p.sub} tenant=${tenantId} role=${p.role}`);
} catch {
client.disconnect(true);
}
}
// ---- مساعدات البث التي تستدعيها الخدمات ----
/** عرض رحلة لسائق محدد. */
offerToDriver(tenantId: string, driverUserId: string, payload: any) {
this.server.to(`tenant:${tenantId}:user:${driverUserId}`).emit('trip:offer', payload);
}
/** تحديث حالة الرحلة للراكب أو السائق. */
tripUpdate(tenantId: string, userId: string, payload: any) {
this.server.to(`tenant:${tenantId}:user:${userId}`).emit('trip:update', payload);
}
/** بث للوحة المشغّل. */
dispatch(tenantId: string, event: string, payload: any) {
this.server.to(`tenant:${tenantId}:dispatch`).emit(event, payload);
}
}
+19
View File
@@ -0,0 +1,19 @@
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { RealtimeGateway } from './realtime.gateway';
@Module({
imports: [
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (cfg: ConfigService) => ({
secret: cfg.get<string>('JWT_SECRET') || 'change_me_jwt_secret',
}),
}),
],
providers: [RealtimeGateway],
exports: [RealtimeGateway],
})
export class RealtimeModule {}