feat: N1d/N2/N3 + قانون فلاتر وإعادة ضبط تطبيق السائق
الباك إند: - تعيين أول أدمن صار يُنشئ المستخدم إن لم يوجد — كان يشترط دخولاً سابقاً، وهي بيضة ودجاجة تمنع الدخول إلى لوحة أي مستأجر جديد أصلاً. - OTP_DEV_MODE كان يفشل مفتوحاً (`!== 'false'`): غياب المتغيّر أو خطأ مطبعي يترك الإنتاج برمز ثابت يفتح كل حساب، ويحرس السحب المالي كذلك. صار `=== 'true'` في الإعداد وفي قارئَيه، مع تحذير عند الإقلاع. - N2: التحقّق من اللوغو عند الرفع (PNG/JPEG · 512+ · مربّع · سقف 5MB) عبر قراءة الترويسة بلا اعتمادية — بدل اكتشاف أيقونة ممطوطة بعد النشر. N3: السكربت يولّد build_config.dart بأعلام const (طبقات docs/22 §1.5)، ويضبط bundle IDs واسم التطبيق والأيقونات/splash، و--build يشغّل Shorebird. فلاتر: - docs/23: قانون مُلزِم للتطبيقين (البنية · Cubit · طبقة الشبكة · الأعلام). - السائق: أُزيل Dart القديم (GetX) مع الإبقاء على الأصيل والإضافات وشهادات التوقيع وShorebird وFirebase الخاص به، وأُعيد هيكلته مطابقاً للراكب. - طبقة الشبكة ترسل x-device-id وتجدّد التوكن عند 401 مرة واحدة فقط. - الحزم موحّدة بين التطبيقين، والـAPI https حصراً في الاثنين. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
3ab74a13a8
commit
de4bbd00ac
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
@@ -19,6 +20,7 @@ import { FEATURES } from '../../common/entitlements/features';
|
||||
import { PAYMENT_METHODS } from '../../common/entitlements/payment-methods';
|
||||
import { PlatformGuard } from '../../common/platform/platform.guard';
|
||||
import { StorageService } from '../../common/storage/storage.service';
|
||||
import { readImageMeta, validateLogo } from '../../common/storage/image-meta';
|
||||
|
||||
@ApiTags('tenants')
|
||||
@Controller()
|
||||
@@ -142,14 +144,28 @@ export class TenantsController {
|
||||
return this.tenants.setPaymentMethods(id, methods);
|
||||
}
|
||||
|
||||
/** رفع لوغو المستأجر (docs/22 — N2) → يُخزَّن ويُربط في الهوية البصرية. */
|
||||
/**
|
||||
* رفع لوغو المستأجر (docs/22 — N2) → يُخزَّن ويُربط في الهوية البصرية.
|
||||
*
|
||||
* يُتحقَّق منه هنا لا عند البناء: لوغو صغير أو مستطيل يُنتج أيقونة ممطوطة
|
||||
* في **كل** تطبيقات المستأجر، ولا يُكتشف إلا بعد بناءٍ ونشرٍ كاملين.
|
||||
* الحدّ 5MB يمنع رفعاً غير محدود يملأ قرص السيرفر (وهو مزدحم أصلاً).
|
||||
*/
|
||||
@ApiSecurity('x-platform-secret')
|
||||
@UseGuards(PlatformGuard)
|
||||
@Post('admin/tenants/:id/logo')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
@UseInterceptors(FileInterceptor('file', { limits: { fileSize: 5 * 1024 * 1024 } }))
|
||||
async logo(@Param('id') id: string, @UploadedFile() file: any) {
|
||||
const key = await this.storage.save(id, 'branding', file.buffer, file.originalname);
|
||||
return this.tenants.setBranding(id, { logo_key: key });
|
||||
if (!file?.buffer) throw new BadRequestException('لا ملف مرفوع (الحقل: file)');
|
||||
const problem = validateLogo(file.buffer);
|
||||
if (problem) throw new BadRequestException(problem);
|
||||
|
||||
const meta = readImageMeta(file.buffer)!;
|
||||
// نوحّد الامتداد من المحتوى لا من الاسم: `logo.png` قد يكون JPEG فعلياً،
|
||||
// فتُرسَل ترويسة Content-Type خاطئة من نقطة اللوغو العامة.
|
||||
const key = await this.storage.save(id, 'branding', file.buffer, `logo.${meta.format === 'png' ? 'png' : 'jpg'}`);
|
||||
const tenant = await this.tenants.setBranding(id, { logo_key: key });
|
||||
return { ...tenant, logo: { ...meta, url: `/api/tenant/logo/${tenant.slug}` } };
|
||||
}
|
||||
|
||||
/** الاستحقاقات الفعّالة كما يراها الحارس — للتشخيص ولعرضها في اللوحة. */
|
||||
|
||||
Reference in New Issue
Block a user