feat: المجموعة G — Redis خط أول والقاعدة خط احتياط
الأساس: common/cache — كاش-جانبي فوق Redis. مبدأ صارم: فشل Redis لا يُسقط الطلب (يُسجَّل ويُرجَع للقاعدة) — الكاش تحسين أداء لا مصدر حقيقة. - G1: لغة المستخدم في Redis (كانت استعلاماً قبل كل إشعار)؛ تُكتب عند PATCH /users/me فيصير الإصابة دائمة - G2/G3: توكنات FCM في Redis — يُبطَل عند register وعند اكتشاف توكن ميت. البث الجماعي صار بلا استعلامات قاعدة - G4: كاش tenant resolve/config + إبطال عند الإنشاء - G5: كاش التعرفة و ride-types + إبطال صريح عند أي إنشاء - G6: التقييم يتراكم في Redis، والقاعدة تُكتب مرة واحدة يومياً لكل طرف (حجز الكتابة ذرّي بـLua). التأخير مقصود ليبعد الاحتكاك بين الطرفين. أُضيف ratings.target_user_id و users.rating — تقييم السائق للراكب كان يُخزَّن بلا هدف فلا يُجمَّع أبداً - كاش الخرائط (route/reverse/geocode) بإحداثيات مقرَّبة 4 خانات (~11م): نداء انطلق كان يهيمن على p50 (2.9s). لا يُخزَّن الرجوع لخط مستقيم ولا استجابة فاشلة تصحيح: ادّعيت سابقاً أن tenants.resolve يعمل على كل request — خطأ. tenant_id يأتي من الـJWT؛ resolve يُستدعى عند الدخول و3 كنترولرات فقط. الحمل الأثقل فعلياً هو رفع موقع السائق (المجموعة H). هجرة: RatingsBothParties. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
857652404b
commit
a83dd7e5e5
@@ -2,12 +2,14 @@ import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Tenant } from '../../database/entities/tenant.entity';
|
||||
import { CacheService, CacheKeys, TTL } from '../../common/cache/cache.service';
|
||||
|
||||
@Injectable()
|
||||
export class TenantsService {
|
||||
constructor(
|
||||
@InjectRepository(Tenant)
|
||||
private readonly repo: Repository<Tenant>,
|
||||
private readonly cache: CacheService,
|
||||
) {}
|
||||
|
||||
findAll(): Promise<Tenant[]> {
|
||||
@@ -27,16 +29,26 @@ export class TenantsService {
|
||||
*/
|
||||
async resolve(idOrSlug: string): Promise<Tenant | null> {
|
||||
if (!idOrSlug) return null;
|
||||
const bySlug = await this.findBySlug(idOrSlug);
|
||||
if (bySlug) return bySlug;
|
||||
if (TenantsService.UUID_RE.test(idOrSlug)) {
|
||||
return this.repo.findOne({ where: { id: idOrSlug } });
|
||||
}
|
||||
return null;
|
||||
// Redis خط أول (docs/17 — G4): المستأجر يتغيّر نادراً جداً.
|
||||
return this.cache.wrap(CacheKeys.tenant(idOrSlug), TTL.tenant, async () => {
|
||||
const bySlug = await this.findBySlug(idOrSlug);
|
||||
if (bySlug) return bySlug;
|
||||
if (TenantsService.UUID_RE.test(idOrSlug)) {
|
||||
return this.repo.findOne({ where: { id: idOrSlug } });
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
create(data: Partial<Tenant>): Promise<Tenant> {
|
||||
return this.repo.save(this.repo.create(data));
|
||||
async create(data: Partial<Tenant>): Promise<Tenant> {
|
||||
const saved = await this.repo.save(this.repo.create(data));
|
||||
await this.invalidate(saved);
|
||||
return saved;
|
||||
}
|
||||
|
||||
/** يُبطل مفتاحَي المستأجر (بالـslug وبالـUUID) — يُنادى بعد أي تعديل عليه. */
|
||||
async invalidate(tenant: Pick<Tenant, 'id' | 'slug'>): Promise<void> {
|
||||
await this.cache.del(CacheKeys.tenant(tenant.slug), CacheKeys.tenant(tenant.id));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,7 +56,8 @@ export class TenantsService {
|
||||
* كل ما يمكن جعله ديناميكياً (نصوص، ميزات، ألوان، دفع) يأتي من هنا — راجع docs/06.
|
||||
*/
|
||||
async config(slug: string) {
|
||||
const t = await this.findBySlug(slug);
|
||||
// عبر resolve ليستفيد من الكاش — كل تطبيق يناديها عند الإقلاع.
|
||||
const t = await this.resolve(slug);
|
||||
if (!t) return null;
|
||||
return {
|
||||
slug: t.slug,
|
||||
|
||||
Reference in New Issue
Block a user