Merge branch 'develop'
Some checks failed
Deploy Canina / deploy (push) Successful in 48s
E2E Playwright Tests / Run Full E2E & Security Suites (push) Failing after 6s

This commit is contained in:
parsa aghaei 2026-09-08 12:57:47 +03:30
commit aa5b35eff8
24 changed files with 158204 additions and 5768 deletions

View File

@ -865,3 +865,21 @@ model MenuItem {
@@index([parentId])
@@map("menu_items")
}
model ApiKey {
id String @id @default(uuid()) @db.Uuid
name String @db.VarChar(150)
keyHash String @unique @map("key_hash") @db.VarChar(255)
keyPrefix String @map("key_prefix") @db.VarChar(30)
scopes String @default("*") @db.Text
status String @default("ACTIVE") @db.VarChar(20)
expiresAt DateTime? @map("expires_at") @db.Timestamptz()
lastUsedAt DateTime? @map("last_used_at") @db.Timestamptz()
description String? @db.VarChar(255)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz()
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz()
@@index([keyHash])
@@index([status])
@@map("api_keys")
}

View File

@ -15,6 +15,8 @@ import { PetsController } from './pets.controller';
import { PetsService } from './pets.service';
import { SslController } from './ssl.controller';
import { SslService } from './ssl.service';
import { ApiKeysController } from './api-keys.controller';
import { ApiKeysService } from './api-keys.service';
import { PrismaModule } from '../prisma/prisma.module';
import { RedisModule } from '../redis/redis.module';
@ -29,6 +31,7 @@ import { RedisModule } from '../redis/redis.module';
WikiController,
PetsController,
SslController,
ApiKeysController,
],
providers: [
AdminService,
@ -39,6 +42,8 @@ import { RedisModule } from '../redis/redis.module';
WikiService,
PetsService,
SslService,
ApiKeysService,
],
exports: [ApiKeysService],
})
export class AdminModule {}

View File

@ -0,0 +1,67 @@
import {
Controller,
Get,
Post,
Delete,
Patch,
Body,
Param,
UseGuards,
HttpStatus,
} from '@nestjs/common';
import { ApiKeysService } from './api-keys.service';
import { CreateApiKeyDto } from './dto/api-key.dto';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { ApiTags, ApiBearerAuth, ApiOperation, ApiResponse } from '@nestjs/swagger';
@ApiTags('Admin - مدیریت کلیدهای دسترسی (API Keys)')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Controller('admin/api-keys')
export class ApiKeysController {
constructor(private readonly apiKeysService: ApiKeysService) {}
@Get()
@ApiOperation({ summary: 'دریافت لیست کلیدهای دسترسی API' })
@ApiResponse({ status: HttpStatus.OK, description: 'لیست کلیدها با موفقیت دریافت شد' })
async findAll() {
const keys = await this.apiKeysService.findAll();
return {
success: true,
data: keys,
};
}
@Post()
@ApiOperation({ summary: 'ایجاد یک API Key جدید' })
@ApiResponse({ status: HttpStatus.CREATED, description: 'کلید جدید با موفقیت ایجاد شد' })
async create(@Body() dto: CreateApiKeyDto) {
const result = await this.apiKeysService.create(dto);
return {
success: true,
message: 'کلید دسترسی با موفقیت ایجاد شد. لطفاً آن را در جای امن ذخیره نمایید.',
data: result,
};
}
@Patch(':id/toggle')
@ApiOperation({ summary: 'تغییر وضعیت فعال/غیرفعال کلید' })
async toggleStatus(@Param('id') id: string) {
const updated = await this.apiKeysService.toggleStatus(id);
return {
success: true,
message: 'وضعیت کلید با موفقیت به‌روزرسانی شد',
data: updated,
};
}
@Delete(':id')
@ApiOperation({ summary: 'حذف کلید دسترسی' })
async delete(@Param('id') id: string) {
await this.apiKeysService.delete(id);
return {
success: true,
message: 'کلید دسترسی با موفقیت حذف شد',
};
}
}

View File

@ -0,0 +1,157 @@
import {
Injectable,
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateApiKeyDto } from './dto/api-key.dto';
import * as crypto from 'crypto';
@Injectable()
export class ApiKeysService {
constructor(private readonly prisma: PrismaService) {}
private hashKey(key: string): string {
return crypto.createHash('sha256').update(key).digest('hex');
}
async findAll() {
const keys = await this.prisma.apiKey.findMany({
orderBy: { createdAt: 'desc' },
select: {
id: true,
name: true,
keyPrefix: true,
scopes: true,
status: true,
description: true,
expiresAt: true,
lastUsedAt: true,
createdAt: true,
updatedAt: true,
},
});
return keys.map((k) => ({
...k,
scopes: k.scopes ? k.scopes.split(',').map((s) => s.trim()) : ['*'],
isExpired: k.expiresAt ? new Date(k.expiresAt) < new Date() : false,
}));
}
async create(dto: CreateApiKeyDto) {
// Generate secure random key: cn_live_<32 hex chars>
const randomBytes = crypto.randomBytes(24).toString('hex');
const rawKey = `cn_live_${randomBytes}`;
const keyPrefix = rawKey.substring(0, 12);
const keyHash = this.hashKey(rawKey);
const scopesStr = dto.scopes && dto.scopes.length > 0 ? dto.scopes.join(',') : '*';
const apiKey = await this.prisma.apiKey.create({
data: {
name: dto.name,
keyHash,
keyPrefix,
scopes: scopesStr,
description: dto.description || null,
expiresAt: dto.expiresAt ? new Date(dto.expiresAt) : null,
status: 'ACTIVE',
},
select: {
id: true,
name: true,
keyPrefix: true,
scopes: true,
status: true,
description: true,
expiresAt: true,
createdAt: true,
},
});
return {
apiKey: {
...apiKey,
scopes: apiKey.scopes.split(','),
},
rawKey, // Return raw secret only once upon creation
};
}
async toggleStatus(id: string) {
const key = await this.prisma.apiKey.findUnique({ where: { id } });
if (!key) {
throw new NotFoundException('کلید یافت نشد');
}
const nextStatus = key.status === 'ACTIVE' ? 'REVOKED' : 'ACTIVE';
const updated = await this.prisma.apiKey.update({
where: { id },
data: { status: nextStatus },
select: {
id: true,
name: true,
keyPrefix: true,
scopes: true,
status: true,
expiresAt: true,
},
});
return {
...updated,
scopes: updated.scopes.split(','),
};
}
async delete(id: string) {
const key = await this.prisma.apiKey.findUnique({ where: { id } });
if (!key) {
throw new NotFoundException('کلید یافت نشد');
}
await this.prisma.apiKey.delete({ where: { id } });
return { success: true };
}
async validateKey(rawKey: string) {
if (!rawKey || typeof rawKey !== 'string' || !rawKey.startsWith('cn_live_')) {
throw new UnauthorizedException('API Key نامعتبر است');
}
const keyHash = this.hashKey(rawKey);
const key = await this.prisma.apiKey.findUnique({
where: { keyHash },
});
if (!key) {
throw new UnauthorizedException('API Key یافت نشد یا معتبر نیست');
}
if (key.status !== 'ACTIVE') {
throw new UnauthorizedException('این API Key باطل یا غیرفعال شده است');
}
if (key.expiresAt && new Date(key.expiresAt) < new Date()) {
throw new UnauthorizedException('این API Key منقضی شده است');
}
// Update lastUsedAt asynchronously in background
this.prisma.apiKey
.update({
where: { id: key.id },
data: { lastUsedAt: new Date() },
})
.catch(() => {});
return {
id: key.id,
name: key.name,
role: 'ADMIN', // Grants admin level access for n8n automation
email: 'apikey-system@canina.ir',
scopes: key.scopes.split(',').map((s) => s.trim()),
isApiKey: true,
};
}
}

View File

@ -0,0 +1,24 @@
import { IsString, IsNotEmpty, IsOptional, IsArray } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class CreateApiKeyDto {
@ApiProperty({ description: 'نام یا شناسه کلید', example: 'ورک‌فلو هماهنگی سفارشات n8n' })
@IsString()
@IsNotEmpty()
name: string;
@ApiPropertyOptional({ description: 'توضیحات تکمیلی درباره کلید', example: 'استفاده در وب‌هوک سفارشات' })
@IsString()
@IsOptional()
description?: string;
@ApiPropertyOptional({ description: 'سطوح دسترسی (scopes)', example: ['orders:read', 'orders:write'] })
@IsArray()
@IsOptional()
scopes?: string[];
@ApiPropertyOptional({ description: 'تاریخ انقضا به فرمت ISO (در صورت خالی بودن، بدون انقضا خواهد بود)', example: '2026-12-31T23:59:59.000Z' })
@IsString()
@IsOptional()
expiresAt?: string;
}

View File

@ -1,15 +1,17 @@
import { Module } from '@nestjs/common';
import { Module, forwardRef } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './jwt.strategy';
import { UsersModule } from '../users/users.module';
import { AdminModule } from '../admin/admin.module';
import { getJwtSecret } from './auth.constants';
@Module({
imports: [
UsersModule,
forwardRef(() => AdminModule),
PassportModule,
JwtModule.registerAsync({
useFactory: () => ({

View File

@ -1,8 +1,45 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import {
Injectable,
ExecutionContext,
UnauthorizedException,
Inject,
forwardRef,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiKeysService } from '../admin/api-keys.service';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(
@Inject(forwardRef(() => ApiKeysService))
private readonly apiKeysService?: ApiKeysService,
) {
super();
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const apiKeyHeader =
request.headers['x-api-key'] ||
(request.headers['authorization']?.startsWith('ApiKey ')
? request.headers['authorization'].replace('ApiKey ', '').trim()
: null);
if (apiKeyHeader && this.apiKeysService) {
try {
const apiKeyUser = await this.apiKeysService.validateKey(apiKeyHeader);
request.user = apiKeyUser;
return true;
} catch (err: unknown) {
throw new UnauthorizedException(
(err as Error)?.message || 'کلید API نامعتبر است',
);
}
}
return super.canActivate(context) as Promise<boolean>;
}
handleRequest<TUser = Record<string, unknown>>(
err: unknown,
user: TUser | false,
@ -10,7 +47,7 @@ export class JwtAuthGuard extends AuthGuard('jwt') {
if (err || !user) {
throw (
(err as Error) ||
new UnauthorizedException('لطفا ابتدا وارد حساب کاربری خود شوید')
new UnauthorizedException('لطفا ابتدا وارد حساب کاربری خود شوید یا API Key معتبر ارسال کنید')
);
}
return user;

View File

@ -36,6 +36,7 @@ import {
Menu,
RotateCcw,
Activity,
Key,
} from 'lucide-react';
import api from '../services/api';
@ -188,6 +189,7 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
{ icon: DollarSign, label: 'تنظیمات مالی و مالیات', path: '/settings/financial' },
{ icon: MessageSquare, label: 'درگاه پیامک (MeliPayamak)', path: '/settings/sms' },
{ icon: ShieldCheck, label: 'گواهی SSL و امنیت', path: '/settings/ssl' },
{ icon: Key, label: 'کلیدهای دسترسی (API Keys)', path: '/settings/api-keys' },
{ icon: Globe, label: 'تنظیمات سئو (SEO)', path: '/settings/seo' },
{ icon: Sliders, label: 'تنظیمات سیستمی پیشرفته', path: '/settings/system' },
],

View File

@ -0,0 +1,541 @@
import { useState, useEffect } from 'react';
import {
Key,
Plus,
Trash2,
Copy,
Check,
Shield,
Clock,
AlertTriangle,
RefreshCw,
Power,
ExternalLink,
Lock,
} from 'lucide-react';
import { toast } from 'react-hot-toast';
import api from '../services/api';
import Spinner from '../components/ui/Spinner';
import Button from '../components/ui/Button';
import type { ApiKeyItem, CreateApiKeyPayload } from '../types/admin';
export default function ApiKeysPage() {
const [keys, setKeys] = useState<ApiKeyItem[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [isCreating, setIsCreating] = useState(false);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
// Success modal showing the created key
const [newlyCreatedKey, setNewlyCreatedKey] = useState<string | null>(null);
const [copied, setCopied] = useState(false);
// Form states
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const [expiryOption, setExpiryOption] = useState<'never' | '30d' | '90d' | '1y'>('never');
const [selectedScopes, setSelectedScopes] = useState<string[]>(['*']);
const scopeOptions = [
{ id: '*', label: 'دسترسی کامل (Full Access)', desc: 'دسترسی نامحدود به تمام بخش‌های API' },
{ id: 'orders:read', label: 'مشاهده سفارشات', desc: 'خواندن لیست و جزئیات سفارش‌ها' },
{ id: 'orders:write', label: 'تغییر وضعیت سفارشات', desc: 'به‌روزرسانی و ثبت وضعیت سفارش‌ها' },
{ id: 'products:read', label: 'مشاهده محصولات', desc: 'دریافت مشخصات، کاتالوگ و موجودی کالاها' },
{ id: 'products:write', label: 'ویرایش محصولات', desc: 'تغییر قیمت، موجودی یا افزودن محصول' },
{ id: 'users:read', label: 'مشاهده کاربران', desc: 'دریافت اطلاعات و آمار کاربران' },
];
const fetchKeys = async () => {
try {
setIsLoading(true);
const res = await api.get('/admin/api-keys');
if (res.data?.success) {
setKeys(res.data.data || []);
}
} catch (err) {
console.error('Failed to fetch api keys', err);
toast.error('خطا در بارگذاری کلیدهای دسترسی');
} finally {
setIsLoading(false);
}
};
useEffect(() => {
fetchKeys();
}, []);
const handleCreate = async (e: React.FormEvent) => {
e.preventDefault();
if (!name.trim()) {
toast.error('لطفاً عنوان کلید را وارد کنید');
return;
}
try {
setIsCreating(true);
let expiresAt: string | undefined = undefined;
const now = new Date();
if (expiryOption === '30d') {
expiresAt = new Date(now.setDate(now.getDate() + 30)).toISOString();
} else if (expiryOption === '90d') {
expiresAt = new Date(now.setDate(now.getDate() + 90)).toISOString();
} else if (expiryOption === '1y') {
expiresAt = new Date(now.setFullYear(now.getFullYear() + 1)).toISOString();
}
const payload: CreateApiKeyPayload = {
name: name.trim(),
description: description.trim() || undefined,
scopes: selectedScopes.includes('*') ? ['*'] : selectedScopes,
expiresAt,
};
const res = await api.post('/admin/api-keys', payload);
if (res.data?.success) {
toast.success('کلید دسترسی با موفقیت ایجاد شد');
setNewlyCreatedKey(res.data.data.rawKey);
setIsCreateModalOpen(false);
setName('');
setDescription('');
setExpiryOption('never');
setSelectedScopes(['*']);
fetchKeys();
}
} catch (err) {
console.error('Failed to create api key', err);
} finally {
setIsCreating(false);
}
};
const handleToggleStatus = async (id: string) => {
try {
const res = await api.patch(`/admin/api-keys/${id}/toggle`);
if (res.data?.success) {
toast.success(res.data.message || 'وضعیت کلید تغییر کرد');
fetchKeys();
}
} catch (err) {
console.error('Failed to toggle status', err);
}
};
const handleDelete = async (id: string, keyName: string) => {
if (!window.confirm(`آیا از حذف کلید دسترسی "${keyName}" اطمینان دارید؟ تمام وب‌هوک‌ها یا سیستم‌های متصل به آن قطع خواهند شد.`)) {
return;
}
try {
const res = await api.delete(`/admin/api-keys/${id}`);
if (res.data?.success) {
toast.success('کلید دسترسی با موفقیت حذف شد');
setKeys((prev) => prev.filter((k) => k.id !== id));
}
} catch (err) {
console.error('Failed to delete key', err);
}
};
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
setCopied(true);
toast.success('کلید در کلیپ‌بورد کپی شد');
setTimeout(() => setCopied(false), 3000);
};
const toggleScope = (scopeId: string) => {
if (scopeId === '*') {
setSelectedScopes(['*']);
return;
}
setSelectedScopes((prev) => {
const withoutAll = prev.filter((s) => s !== '*');
if (withoutAll.includes(scopeId)) {
const next = withoutAll.filter((s) => s !== scopeId);
return next.length === 0 ? ['*'] : next;
} else {
return [...withoutAll, scopeId];
}
});
};
return (
<div className="space-y-6 font-vazir text-right" dir="rtl">
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 bg-white p-6 rounded-2xl border border-gray-100 shadow-sm">
<div>
<div className="flex items-center gap-2">
<div className="p-2 bg-purple-50 text-purple-600 rounded-xl">
<Key className="w-6 h-6" />
</div>
<h1 className="text-xl font-black text-gray-900">کلیدهای دسترسی API (API Keys)</h1>
</div>
<p className="text-sm text-gray-500 mt-1">
ایجاد و مدیریت توکنهای دائمی برای اتصال سرویسهای اتوماسیون (نظیر n8n، Zapier یا اسکریپتها)
</p>
</div>
<div className="flex items-center gap-3">
<Button
variant="outline"
onClick={fetchKeys}
disabled={isLoading}
className="flex items-center gap-1.5"
>
<RefreshCw className={`w-4 h-4 ${isLoading ? 'animate-spin' : ''}`} />
<span>بروزرسانی</span>
</Button>
<Button
variant="primary"
onClick={() => setIsCreateModalOpen(true)}
className="flex items-center gap-1.5 bg-purple-600 hover:bg-purple-700 text-white font-bold"
>
<Plus className="w-4 h-4" />
<span>ساخت API Key جدید</span>
</Button>
</div>
</div>
{/* Usage Guide in n8n */}
<div className="bg-gradient-to-r from-purple-50/70 to-indigo-50/70 border border-purple-100/80 rounded-2xl p-5 text-gray-700">
<div className="flex items-start gap-3">
<div className="p-2 bg-purple-600 text-white rounded-lg mt-0.5 shrink-0">
<ExternalLink className="w-4 h-4" />
</div>
<div className="space-y-1 text-xs sm:text-sm">
<h4 className="font-bold text-purple-950 text-sm">راهنمای استفاده در n8n یا ابزارهای مشابه</h4>
<p className="text-gray-600 leading-relaxed">
در نودهای <strong className="text-gray-900">HTTP Request</strong> ورکفلوهای n8n، در بخش <strong>Authentication</strong> گزینه <strong>Header Auth</strong> را انتخاب کنید. سپس نام هدر را برابر <code className="bg-purple-100 text-purple-800 px-1.5 py-0.5 rounded font-mono font-bold">x-api-key</code> و مقدار آن را برابر کلید تولید شده قرار دهید. کلیدها تا زمان انقضا یا باطل شدن به صورت دائمی معتبر خواهند بود.
</p>
</div>
</div>
</div>
{/* Keys Table */}
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden">
{isLoading ? (
<div className="p-16 flex flex-col items-center justify-center gap-3">
<Spinner className="w-8 h-8 text-purple-600" />
<span className="text-sm font-bold text-gray-500">در حال بارگذاری لیست کلیدها...</span>
</div>
) : keys.length === 0 ? (
<div className="p-16 text-center">
<div className="w-16 h-16 bg-purple-50 text-purple-600 rounded-2xl flex items-center justify-center mx-auto mb-4">
<Key className="w-8 h-8" />
</div>
<h3 className="text-base font-bold text-gray-900 mb-1">هیچ کلید دسترسی تعریف نشده است</h3>
<p className="text-sm text-gray-500 max-w-md mx-auto mb-6">
جهت برقراری ارتباط ایمن با ورکفلوهای n8n، میتوانید همین حالا اولین API Key خود را ایجاد کنید.
</p>
<Button
variant="primary"
onClick={() => setIsCreateModalOpen(true)}
className="bg-purple-600 hover:bg-purple-700 text-white font-bold"
>
<Plus className="w-4 h-4 ml-1" />
ساخت اولین کلید
</Button>
</div>
) : (
<div className="overflow-x-auto">
<table className="w-full text-right border-collapse text-sm">
<thead>
<tr className="bg-gray-50/80 border-b border-gray-100 text-gray-500 font-bold">
<th className="py-4 px-6">عنوان کلید</th>
<th className="py-4 px-6">پیشنمایش کلید</th>
<th className="py-4 px-6">دسترسیها (Scopes)</th>
<th className="py-4 px-6">تاریخ انقضا</th>
<th className="py-4 px-6">آخرین استفاده</th>
<th className="py-4 px-6 text-center">وضعیت</th>
<th className="py-4 px-6 text-center">عملیات</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100">
{keys.map((k) => (
<tr key={k.id} className="hover:bg-gray-50/50 transition-colors">
<td className="py-4 px-6">
<div className="font-bold text-gray-900">{k.name}</div>
{k.description && (
<div className="text-xs text-gray-400 mt-0.5">{k.description}</div>
)}
</td>
<td className="py-4 px-6">
<div className="inline-flex items-center gap-1.5 font-mono text-xs bg-gray-100 px-2.5 py-1 rounded-md text-gray-700 font-semibold">
<span>{k.keyPrefix}</span>
<span className="text-gray-400"></span>
</div>
</td>
<td className="py-4 px-6">
<div className="flex flex-wrap gap-1 max-w-xs">
{k.scopes.map((s, idx) => (
<span
key={idx}
className={`text-xs px-2 py-0.5 rounded-full font-medium ${
s === '*'
? 'bg-purple-100 text-purple-800'
: 'bg-gray-100 text-gray-700'
}`}
>
{s === '*' ? 'دسترسی کامل' : s}
</span>
))}
</div>
</td>
<td className="py-4 px-6 text-gray-600 text-xs">
{k.expiresAt ? (
<div className="flex items-center gap-1">
<Clock className="w-3.5 h-3.5 text-gray-400" />
<span className={k.isExpired ? 'text-red-600 font-bold' : ''}>
{new Date(k.expiresAt).toLocaleDateString('fa-IR')}
{k.isExpired && ' (منقضی شده)'}
</span>
</div>
) : (
<span className="text-emerald-600 font-semibold">نامحدود (دائمی)</span>
)}
</td>
<td className="py-4 px-6 text-gray-500 text-xs">
{k.lastUsedAt ? (
new Date(k.lastUsedAt).toLocaleString('fa-IR', {
dateStyle: 'short',
timeStyle: 'short',
})
) : (
<span className="text-gray-400">تاکنون استفاده نشده</span>
)}
</td>
<td className="py-4 px-6 text-center">
<span
className={`inline-flex items-center px-2.5 py-1 rounded-full text-xs font-bold ${
k.status === 'ACTIVE' && !k.isExpired
? 'bg-emerald-50 text-emerald-700 border border-emerald-200/60'
: 'bg-red-50 text-red-700 border border-red-200/60'
}`}
>
{k.status === 'ACTIVE' && !k.isExpired ? 'فعال' : 'غیرفعال / باطل'}
</span>
</td>
<td className="py-4 px-6 text-center">
<div className="flex items-center justify-center gap-2">
<button
onClick={() => handleToggleStatus(k.id)}
title={k.status === 'ACTIVE' ? 'غیرفعال کردن کلید' : 'فعال کردن مجدد'}
className={`p-1.5 rounded-lg border transition-colors ${
k.status === 'ACTIVE'
? 'text-gray-500 hover:text-amber-600 hover:bg-amber-50 border-gray-200'
: 'text-gray-500 hover:text-emerald-600 hover:bg-emerald-50 border-gray-200'
}`}
>
<Power className="w-4 h-4" />
</button>
<button
onClick={() => handleDelete(k.id, k.name)}
title="حذف دائمی کلید"
className="p-1.5 rounded-lg border border-gray-200 text-gray-500 hover:text-red-600 hover:bg-red-50 transition-colors"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
{/* Modal: Create API Key */}
{isCreateModalOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4">
<div className="bg-white rounded-2xl w-full max-w-lg overflow-hidden shadow-xl animate-scale-up">
<div className="px-6 py-4 border-b border-gray-100 flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="p-1.5 bg-purple-50 text-purple-600 rounded-lg">
<Key className="w-5 h-5" />
</div>
<h3 className="font-bold text-gray-900 text-base">ساخت کلید دسترسی جدید</h3>
</div>
<button
onClick={() => setIsCreateModalOpen(false)}
className="text-gray-400 hover:text-gray-600 text-xl font-bold"
>
&times;
</button>
</div>
<form onSubmit={handleCreate} className="p-6 space-y-4">
<div>
<label className="block text-xs font-bold text-gray-700 mb-1">
عنوان کلید <span className="text-red-500">*</span>
</label>
<input
type="text"
required
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="مثلاً: ورک‌فلو n8n یا اتصال انبار"
className="w-full px-3.5 py-2 rounded-xl border border-gray-200 text-sm focus:outline-none focus:ring-2 focus:ring-purple-600/20 focus:border-purple-600"
/>
</div>
<div>
<label className="block text-xs font-bold text-gray-700 mb-1">توضیحات (اختیاری)</label>
<input
type="text"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="کاربرد این کلید در کجاست؟"
className="w-full px-3.5 py-2 rounded-xl border border-gray-200 text-sm focus:outline-none focus:ring-2 focus:ring-purple-600/20 focus:border-purple-600"
/>
</div>
<div>
<label className="block text-xs font-bold text-gray-700 mb-1">مدت اعتبار و انقضا</label>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
{[
{ id: 'never', label: 'بدون انقضا (دائمی)' },
{ id: '30d', label: '۳۰ روزه' },
{ id: '90d', label: '۹۰ روزه' },
{ id: '1y', label: 'یک‌ساله' },
].map((opt) => (
<button
key={opt.id}
type="button"
onClick={() => setExpiryOption(opt.id as any)}
className={`py-2 px-2 text-xs rounded-xl border font-bold transition-all ${
expiryOption === opt.id
? 'bg-purple-50 border-purple-600 text-purple-700 shadow-sm'
: 'bg-gray-50 border-gray-200 text-gray-600 hover:bg-gray-100'
}`}
>
{opt.label}
</button>
))}
</div>
</div>
<div>
<label className="block text-xs font-bold text-gray-700 mb-2">سطح دسترسی (Scopes)</label>
<div className="space-y-2 max-h-48 overflow-y-auto pr-1">
{scopeOptions.map((scope) => {
const isChecked = selectedScopes.includes(scope.id);
return (
<div
key={scope.id}
onClick={() => toggleScope(scope.id)}
className={`p-2.5 rounded-xl border cursor-pointer transition-all flex items-start gap-2.5 ${
isChecked
? 'bg-purple-50/50 border-purple-300'
: 'bg-white border-gray-200 hover:bg-gray-50'
}`}
>
<input
type="checkbox"
checked={isChecked}
onChange={() => {}}
className="mt-0.5 rounded text-purple-600 focus:ring-purple-500"
/>
<div className="text-xs">
<div className="font-bold text-gray-900">{scope.label}</div>
<div className="text-gray-500 text-[11px]">{scope.desc}</div>
</div>
</div>
);
})}
</div>
</div>
<div className="pt-4 border-t border-gray-100 flex items-center justify-end gap-2">
<Button
type="button"
variant="outline"
onClick={() => setIsCreateModalOpen(false)}
>
انصراف
</Button>
<Button
type="submit"
variant="primary"
disabled={isCreating}
className="bg-purple-600 hover:bg-purple-700 text-white font-bold"
>
{isCreating ? 'در حال ایجاد...' : 'تولید کلید دسترسی'}
</Button>
</div>
</form>
</div>
</div>
)}
{/* Modal: Show Newly Created Key (Once-only) */}
{newlyCreatedKey && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
<div className="bg-white rounded-2xl w-full max-w-lg overflow-hidden shadow-2xl animate-scale-up border-2 border-emerald-500">
<div className="bg-emerald-600 text-white px-6 py-4 flex items-center gap-3">
<Shield className="w-6 h-6" />
<div>
<h3 className="font-bold text-base">کلید دسترسی با موفقیت تولید شد</h3>
<p className="text-xs text-emerald-100">لطفاً کلید زیر را فوراً ذخیره کنید</p>
</div>
</div>
<div className="p-6 space-y-4">
<div className="bg-amber-50 border border-amber-200 rounded-xl p-3.5 flex items-start gap-2.5 text-xs text-amber-900">
<AlertTriangle className="w-5 h-5 text-amber-600 shrink-0 mt-0.5" />
<p className="leading-relaxed">
<strong>هشدار امنیتی:</strong> این کلید محرمانه <strong>تنها یکبار</strong> به شما نمایش داده میشود. به محض بستن این پنجره، دیگر امکان بازیابی متن کامل کلید وجود نخواهد داشت. لطفاً آن را در محلی امن (یا در بخش Credentials نود n8n) کپی نمایید.
</p>
</div>
<div>
<label className="block text-xs font-bold text-gray-700 mb-1.5">کلید دسترسی (API Key):</label>
<div className="relative">
<input
type="text"
readOnly
value={newlyCreatedKey}
className="w-full bg-gray-50 border border-gray-300 font-mono text-xs sm:text-sm px-3.5 py-3 rounded-xl pr-3 pl-24 text-gray-900 select-all font-bold"
/>
<button
onClick={() => copyToClipboard(newlyCreatedKey)}
className="absolute left-1.5 top-1.5 bottom-1.5 px-3 bg-purple-600 hover:bg-purple-700 text-white rounded-lg text-xs font-bold flex items-center gap-1.5 shadow-sm transition-all"
>
{copied ? (
<>
<Check className="w-3.5 h-3.5" />
<span>کپی شد</span>
</>
) : (
<>
<Copy className="w-3.5 h-3.5" />
<span>کپی کلید</span>
</>
)}
</button>
</div>
</div>
<div className="pt-2 flex justify-end">
<Button
variant="primary"
onClick={() => setNewlyCreatedKey(null)}
className="bg-gray-900 hover:bg-black text-white font-bold"
>
کلید را ذخیره کردم، بستن پنجره
</Button>
</div>
</div>
</div>
</div>
)}
</div>
);
}

View File

@ -38,6 +38,7 @@ const ShippingSettingsPage = lazyWithRetry(() => import('../pages/ShippingSettin
const SystemSettingsPage = lazyWithRetry(() => import('../pages/SystemSettingsPage'));
const SmsSettingsPage = lazyWithRetry(() => import('../pages/SmsSettingsPage'));
const SslSettingsPage = lazyWithRetry(() => import('../pages/SslSettingsPage'));
const ApiKeysPage = lazyWithRetry(() => import('../pages/ApiKeysPage'));
const Transactions = lazyWithRetry(() => import('../pages/Transactions'));
const Tickets = lazyWithRetry(() => import('../pages/Tickets'));
const Reviews = lazyWithRetry(() => import('../pages/Reviews'));
@ -79,6 +80,7 @@ export const router = createBrowserRouter([
{ path: 'settings/payment-methods', element: <PaymentGatewaysPage /> },
{ path: 'settings/shipping', element: <ShippingSettingsPage /> },
{ path: 'settings/ssl', element: <SslSettingsPage /> },
{ path: 'settings/api-keys', element: <ApiKeysPage /> },
{ path: 'settings/sms', element: <SmsSettingsPage /> },
{ path: 'settings/seo', element: <SeoSettingsPage /> },
{ path: 'settings/financial', element: <FinancialSettingsPage /> },

View File

@ -226,4 +226,25 @@ export interface PricingSettings {
defaultWholesaleMarginPercent: number;
}
export interface ApiKeyItem {
id: string;
name: string;
keyPrefix: string;
scopes: string[];
status: 'ACTIVE' | 'REVOKED';
description?: string | null;
expiresAt?: string | null;
lastUsedAt?: string | null;
createdAt: string;
updatedAt?: string;
isExpired?: boolean;
}
export interface CreateApiKeyPayload {
name: string;
description?: string;
scopes?: string[];
expiresAt?: string;
}

View File

@ -1,7 +1,7 @@
{
"0": "Roles",
"1": "app.module.ts",
"2": "WikiController",
"2": "ApiKeysService",
"3": "productService.ts",
"4": "PetsController",
"5": "CmsController",
@ -13,14 +13,14 @@
"11": "UsersService",
"12": "index.ts",
"13": "app-audit-verification.e2e-spec.js",
"14": "CheckoutPage.tsx",
"14": "toPersian",
"15": "src/services/api.ts",
"16": "DoctorQueryDto",
"17": "schema.ts",
"18": "JwtAuthGuard",
"19": "admin.controller.ts",
"20": "CreateVideoDto",
"21": "HomeClient.tsx",
"21": "useSettingsStore",
"22": "AdminService",
"23": "MenuService",
"24": "BE-001",
@ -47,27 +47,27 @@
"45": "What You Must Do When Invoked",
"46": "20260526145407_init/migration.sql",
"47": "IngredientsService",
"48": "toPersian",
"48": "Button.tsx",
"49": "devDependencies",
"50": "devDependencies",
"51": "BlogsController",
"52": "PrescriptionsService",
"53": "SmartAdvisorService",
"54": "Button.tsx",
"54": "Modal.tsx",
"55": "UITexts.tsx",
"56": "Orders.tsx",
"56": "PrescriptionsManager.tsx",
"57": "Role & Core Objective",
"58": "RedisService",
"59": "compilerOptions",
"60": "CreateUserDto",
"61": "ProductPage.tsx",
"62": "ReportsController",
"62": "admin.module.ts",
"63": "dependencies",
"64": "compilerOptions",
"65": "admin.service.ts",
"66": "AdminQueryDto",
"67": "pets/pets.controller.ts",
"68": "lib/services/api.ts",
"68": "components/Skeleton.tsx",
"69": "Required Review Group Closures",
"70": "compilerOptions",
"71": "getPageMetadata",
@ -75,7 +75,7 @@
"73": "Operational Rules & Boundaries",
"74": "WikiController",
"75": "PetsController",
"76": "RevalidationService",
"76": "ProductsService",
"77": "seo.module.ts",
"78": "rss.xml/route.ts",
"79": "🏢 AI Software Agency — Master Orchestration Protocol v3",
@ -89,7 +89,7 @@
"87": "dependencies",
"88": "CreateEBankCheckoutDto",
"89": "seed-products.ts",
"90": "useSettingsStore",
"90": "ClientLayout.tsx",
"91": "Reconciled Audit Roles & Assignments",
"92": "OrdersService",
"93": "auth.service.ts",
@ -112,9 +112,9 @@
"110": "Operational Rules & Boundaries",
"111": "Operational Rules & Boundaries",
"112": "Operational Rules & Boundaries",
"113": "orderService.ts",
"113": "lib/services/api.ts",
"114": "AppService",
"115": "Spinner.tsx",
"115": "MediaSelector.tsx",
"116": "Vazirmatn Changelog",
"117": "Vazirmatn Font فونت وزیرمتن",
"118": "Operational Rules & Boundaries",
@ -122,13 +122,13 @@
"120": "compilerOptions",
"121": "backend/README.md",
"122": "AdminController",
"123": "ConfirmModal.tsx",
"123": "auth.module.ts",
"124": "Repository Map",
"125": "validate_integrity.js",
"126": "admin-panel/package.json",
"127": "Sahel-Font",
"128": "HomeController",
"129": "LoginDto",
"129": "PaymentService",
"130": "Sahel-Font",
"131": "Role & Core Objective",
"132": "orchestrate.py",
@ -144,14 +144,14 @@
"142": "start-dev.js",
"143": "generate-openapi.js",
"144": "SafeImage.tsx",
"145": "AdminLoginDto",
"145": "Orders.tsx",
"146": "System Discovery",
"147": "torob.controller.ts",
"148": "Reports.tsx",
"149": "RegisterDto",
"147": "products.module.ts",
"148": "PetsService",
"149": "SmsLogQueryDto",
"150": "Product Requirement Document (PRD)",
"151": "@eslint/js",
"152": "AuthService",
"152": "CreateHealthLogDto",
"153": "exclude",
"154": "Baseline Command Plan & Reconciled Command History",
"155": "seo-backfill.ts",
@ -178,7 +178,7 @@
"176": "uploads/[...path]/route.ts",
"177": "app.e2e-spec.js",
"178": "نقشه جامع پوشش تست‌های سرتاسری (E2E Test Coverage Map) — پروژه Canina",
"179": "track/page.tsx",
"179": "CreateReminderDto",
"180": "API Contract Specification",
"181": "⚙️ Backend Technical Review (05_dev_backend)",
"182": "🎨 Frontend & Admin Panel Technical Review (06_dev_frontend)",
@ -268,7 +268,7 @@
"266": "@types/express",
"267": "@types/jest",
"268": "@types/multer",
"269": "@nestjs/swagger",
"269": "menu.module.ts",
"270": "app-audit-verification.e2e-spec.d.ts",
"271": "app.e2e-spec.d.ts",
"272": "Canina Pharma GmbH",
@ -327,8 +327,8 @@
"325": "@types/react-dom",
"326": "@types/supertest",
"327": "eslint-plugin-react-refresh",
"328": "orders.service.ts",
"329": "videos.controller.ts",
"330": "WikiService",
"328": "ZibalCallbackQueryDto",
"329": "faq.module.ts",
"330": "bcrypt",
"331": "eslint-plugin-prettier"
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,334 @@
{
"0": "Roles",
"1": "app.module.ts",
"2": "WikiController",
"3": "productService.ts",
"4": "PetsController",
"5": "CmsController",
"6": "tickets.controller.ts",
"7": "SmsSettingsPage.tsx",
"8": "SmsService",
"9": "devDependencies",
"10": "CreateReviewDto",
"11": "UsersService",
"12": "index.ts",
"13": "app-audit-verification.e2e-spec.js",
"14": "CheckoutPage.tsx",
"15": "src/services/api.ts",
"16": "DoctorQueryDto",
"17": "schema.ts",
"18": "JwtAuthGuard",
"19": "admin.controller.ts",
"20": "CreateVideoDto",
"21": "HomeClient.tsx",
"22": "AdminService",
"23": "MenuService",
"24": "BE-001",
"25": "FE-001",
"26": "ADM-001",
"27": "DB-001",
"28": "TS-001",
"29": "TEST-001",
"30": "DEVOPS-001",
"31": "DOC-001",
"32": "adminRoutes.tsx",
"33": "WholesaleApplyDto",
"34": "B2BService",
"35": "ContactService",
"36": "FaqService",
"37": "راهنمای تست سیستم (Software Testing)",
"38": "Button",
"39": "CategoriesController",
"40": "MediaController",
"41": "What You Must Do When Invoked",
"42": "SslController",
"43": "BannersService",
"44": "TestimonialsService",
"45": "What You Must Do When Invoked",
"46": "20260526145407_init/migration.sql",
"47": "IngredientsService",
"48": "toPersian",
"49": "devDependencies",
"50": "devDependencies",
"51": "BlogsController",
"52": "PrescriptionsService",
"53": "SmartAdvisorService",
"54": "Button.tsx",
"55": "UITexts.tsx",
"56": "Orders.tsx",
"57": "Role & Core Objective",
"58": "RedisService",
"59": "compilerOptions",
"60": "CreateUserDto",
"61": "ProductPage.tsx",
"62": "ReportsController",
"63": "dependencies",
"64": "compilerOptions",
"65": "admin.service.ts",
"66": "AdminQueryDto",
"67": "pets/pets.controller.ts",
"68": "lib/services/api.ts",
"69": "Required Review Group Closures",
"70": "compilerOptions",
"71": "getPageMetadata",
"72": "Operational Rules & Boundaries",
"73": "Operational Rules & Boundaries",
"74": "WikiController",
"75": "PetsController",
"76": "RevalidationService",
"77": "seo.module.ts",
"78": "rss.xml/route.ts",
"79": "🏢 AI Software Agency — Master Orchestration Protocol v3",
"80": "Operational Rules & Boundaries",
"81": "Operational Rules & Boundaries",
"82": "scripts",
"83": "dependencies",
"84": "Role & Core Objective",
"85": "AuthController",
"86": "zibal.service.ts",
"87": "dependencies",
"88": "CreateEBankCheckoutDto",
"89": "seed-products.ts",
"90": "useSettingsStore",
"91": "Reconciled Audit Roles & Assignments",
"92": "OrdersService",
"93": "auth.service.ts",
"94": "Phase 3.1 — Human Review Preparation and Master Backlog Critique",
"95": "getSeoConfig",
"96": "compilerOptions",
"97": "AdminTransactionFilterDto",
"98": "scripts",
"99": "BlogsController",
"100": "Deep Audit Summary Report",
"101": "Operational Rules & Boundaries",
"102": "jest",
"103": "Comprehensive Change Log",
"104": "Products.tsx",
"105": "Operational Rules & Boundaries",
"106": "InitiatePaymentDto",
"107": "PaginationDto",
"108": "PrismaService",
"109": "1. Summary of Integrity Repairs Performed",
"110": "Operational Rules & Boundaries",
"111": "Operational Rules & Boundaries",
"112": "Operational Rules & Boundaries",
"113": "orderService.ts",
"114": "AppService",
"115": "Spinner.tsx",
"116": "Vazirmatn Changelog",
"117": "Vazirmatn Font فونت وزیرمتن",
"118": "Operational Rules & Boundaries",
"119": "compilerOptions",
"120": "compilerOptions",
"121": "backend/README.md",
"122": "AdminController",
"123": "ConfirmModal.tsx",
"124": "Repository Map",
"125": "validate_integrity.js",
"126": "admin-panel/package.json",
"127": "Sahel-Font",
"128": "HomeController",
"129": "LoginDto",
"130": "Sahel-Font",
"131": "Role & Core Objective",
"132": "orchestrate.py",
"133": "backend/package.json",
"134": "blog/page.tsx",
"135": "graphify reference: extra exports and benchmark",
"136": "Phase 2 Final Quality Gate Summary Report",
"137": "Task Modifications Log",
"138": "Install",
"139": "layout.tsx",
"140": "ErrorBoundary",
"141": "application/package.json",
"142": "start-dev.js",
"143": "generate-openapi.js",
"144": "SafeImage.tsx",
"145": "AdminLoginDto",
"146": "System Discovery",
"147": "torob.controller.ts",
"148": "Reports.tsx",
"149": "RegisterDto",
"150": "Product Requirement Document (PRD)",
"151": "@eslint/js",
"152": "AuthService",
"153": "exclude",
"154": "Baseline Command Plan & Reconciled Command History",
"155": "seo-backfill.ts",
"156": "manual-test-scenarios.md",
"157": "ErrorPages.tsx",
"158": "media/[...path]/route.ts",
"159": "with-vpn.sh",
"160": "Architecture Specification",
"161": "Project Health Audit Report",
"162": "nest-cli.json",
"163": "graphify reference: query, path, explain",
"164": "Open Questions",
"165": "Final Phase 2 Audit Closure Report",
"166": "open-browsers.js",
"167": "📝 Active Agent Working Scratchpad",
"168": "🔍 Code Health Audit Review (01_auditor)",
"169": "paginated-response.schema.ts",
"170": "Vazirmatn Font README",
"171": "Omitted File Inspection Report",
"172": "Phase 3.2 / 3.3 — Implementation Readiness & Architectural Finalization Report",
"173": "Phase 3 Audit Traceability Matrix",
"174": "rebuild_honest_ledger.js",
"175": "validate_evidence_grade.js",
"176": "uploads/[...path]/route.ts",
"177": "app.e2e-spec.js",
"178": "نقشه جامع پوشش تست‌های سرتاسری (E2E Test Coverage Map) — پروژه Canina",
"179": "track/page.tsx",
"180": "API Contract Specification",
"181": "⚙️ Backend Technical Review (05_dev_backend)",
"182": "🎨 Frontend & Admin Panel Technical Review (06_dev_frontend)",
"183": "🚀 SEO & Content Strategy Review (12_seo_content)",
"184": "trust-seals/page.tsx",
"185": "wiki/[slug]/page.tsx",
"186": "seed-ui-texts.ts",
"187": "seed-wiki.ts",
"188": "update-blog.dto.ts",
"189": "update-home.dto.ts",
"190": "update-wiki.dto.ts",
"191": "graphify reference: add a URL and watch a folder",
"192": "graphify reference: commit hook and native CLAUDE.md integration",
"193": "graphify reference: incremental update and cluster-only",
"194": "Raw Finding Verification & Disposition Report",
"195": "React + TypeScript + Vite",
"196": "Select.tsx",
"197": "AuthModal.tsx",
"198": "app/page.tsx",
"199": "prisma",
"200": "application/README.md",
"201": "deploy.sh",
"202": "🔒 Security & Performance Review (09_devops_security)",
"203": "👁️ UX & Persona Interface Review (08_visual_qa)",
"204": "class-transformer",
"205": "prisma/scientificTerms.ts",
"206": "seed-blogs.ts",
"207": "seed-custom.ts",
"208": "graphify reference: GitHub clone and cross-repo merge",
"209": "graphify reference: transcribe video and audio",
"210": "Compiler Diagnostic Dispositions",
"211": "Master Task Backlog (Phase 3.3)",
"212": "build_manifest.js",
"213": "generate_classification.js",
"214": "generate_evidence.js",
"215": "generate_ledger.js",
"216": "generate_manifest.js",
"217": "sync_honest_manifest.js",
"218": "sync_manifest.js",
"219": "FormField.tsx",
"220": "globals",
"221": "Textarea.tsx",
"222": "admin-panel/tsconfig.json",
"223": "tailwindcss",
"224": "typescript-eslint",
"225": "next.config.ts",
"226": "Shabnam Font README",
"227": "AGENTS.md",
"228": "rules/graphify.md",
"229": ".agents/workflows/graphify.md",
"230": "instructions.md",
"231": "helmet",
"232": "js-yaml",
"233": "@nestjs/core",
"234": "source-map-support",
"235": "ts-loader",
"236": "ts-node",
"237": "tsconfig-paths",
"238": "@vitejs/plugin-react",
"239": "@types/bcrypt",
"240": "supertest",
"241": "blog.entity.ts",
"242": "home.entity.ts",
"243": "wiki.entity.ts",
"244": "User Profile Photo",
"245": "CLAUDE.md",
"246": ".claude/CLAUDE.md",
"247": "extraction-spec.md",
"248": "Products Table",
"249": "Users Table",
"250": "Architectural Audit Findings",
"251": "Cross Boundary Dependencies & Backend Architecture Specification",
"252": "Next.js Agent Rules & Brand Guidelines",
"253": "robots.ts",
"254": "application/eslint.config.mjs",
"255": "postcss.config.mjs",
"256": "vitest.setup.ts",
"257": "backup_db.sh",
"258": "start.sh",
"259": "reviews/README.md",
"260": "backend/eslint.config.mjs",
"261": "User Login API",
"262": "User Logout API",
"263": "generate-openapi.d.ts",
"264": "@types/compression",
"265": "@nestjs/jwt",
"266": "@types/express",
"267": "@types/jest",
"268": "@types/multer",
"269": "@nestjs/swagger",
"270": "app-audit-verification.e2e-spec.d.ts",
"271": "app.e2e-spec.d.ts",
"272": "Canina Pharma GmbH",
"273": "Pets Table",
"274": "Canina Iran Project Introduction",
"275": "Developer Standards and Architecture",
"276": "Frontend & Admin Architecture Route Map Specification",
"277": "Project Backlog and Tasks",
"278": "eslint.config.js",
"279": "postcss.config.js",
"280": "admin-panel/public/fonts/sahel-font-v3.4.0/CHANGELOG.md",
"281": "shabnam-font-v5.0.1/CHANGELOG.md",
"282": "tailwind.config.js",
"283": "vite.config.ts",
"284": "application/CLAUDE.md",
"285": "application/public/fonts/sahel-font-v3.4.0/CHANGELOG.md",
"286": "Sahel Font Sample",
"287": "Shabnam Font Changelog",
"288": "Vazirmatn Changelog",
"289": "vitest.config.ts",
"290": "Sahel Font Variable Sample",
"291": "Shabnam Font Sample",
"292": "Production Docker Compose",
"293": "Staging Docker Compose",
"294": "ZibalService",
"295": "@eslint/eslintrc",
"296": "@nestjs/throttler",
"297": "ZibalEBankService",
"298": ".initiateOrderPayment",
"299": "@tailwindcss/postcss",
"300": "typescript",
"301": "passport",
"302": "typescript-eslint",
"303": "@nestjs/schematics",
"304": "reflect-metadata",
"305": "swagger-ui-express",
"306": "eslint-plugin-react-hooks",
"307": "eslint-config-prettier",
"308": "tailwindcss",
"309": "axios",
"310": "tailwindcss",
"311": "jest",
"312": "@types/passport-jwt",
"313": "20260526160916_add_ui_texts_and_scientific_terms/migration.sql",
"314": "@nestjs/cli",
"315": "typescript",
"316": "@nestjs/testing",
"317": "revalidate/route.ts",
"318": "MaskableField.tsx",
"319": "@tailwindcss/postcss",
"320": "prettier",
"321": "orders/page.tsx",
"322": "pets/page.tsx",
"323": "ts-jest",
"324": "@types/js-yaml",
"325": "@types/react-dom",
"326": "@types/supertest",
"327": "eslint-plugin-react-refresh",
"328": "orders.service.ts",
"329": "videos.controller.ts",
"330": "WikiService",
"331": "eslint-plugin-prettier"
}

View File

@ -0,0 +1 @@
{"output_tokens": 7105}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,23 @@
# Graph Report - canina (2026-09-06)
# Graph Report - canina (2026-09-08)
## Corpus Check
- 604 files · ~1,121,126 words
- 608 files · ~1,123,866 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 4258 nodes · 7827 edges · 332 communities (218 shown, 114 thin omitted)
- Extraction: 96% EXTRACTED · 4% INFERRED · 0% AMBIGUOUS · INFERRED: 298 edges (avg confidence: 0.79)
- 4302 nodes · 7914 edges · 332 communities (215 shown, 117 thin omitted)
- Extraction: 96% EXTRACTED · 4% INFERRED · 0% AMBIGUOUS · INFERRED: 303 edges (avg confidence: 0.79)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `49eb1e5f`
- Built from commit: `ba9472c9`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
## Community Hubs (Navigation)
- Roles
- app.module.ts
- WikiController
- ApiKeysService
- productService.ts
- PetsController
- CmsController
@ -29,14 +29,14 @@
- UsersService
- index.ts
- app-audit-verification.e2e-spec.js
- CheckoutPage.tsx
- toPersian
- src/services/api.ts
- DoctorQueryDto
- schema.ts
- JwtAuthGuard
- admin.controller.ts
- CreateVideoDto
- HomeClient.tsx
- useSettingsStore
- AdminService
- MenuService
- BE-001
@ -63,27 +63,27 @@
- What You Must Do When Invoked
- 20260526145407_init/migration.sql
- IngredientsService
- toPersian
- Button.tsx
- devDependencies
- devDependencies
- BlogsController
- PrescriptionsService
- SmartAdvisorService
- Button.tsx
- Modal.tsx
- UITexts.tsx
- Orders.tsx
- PrescriptionsManager.tsx
- Role & Core Objective
- RedisService
- compilerOptions
- CreateUserDto
- ProductPage.tsx
- ReportsController
- admin.module.ts
- dependencies
- compilerOptions
- admin.service.ts
- AdminQueryDto
- pets/pets.controller.ts
- lib/services/api.ts
- components/Skeleton.tsx
- Required Review Group Closures
- compilerOptions
- getPageMetadata
@ -91,7 +91,7 @@
- Operational Rules & Boundaries
- WikiController
- PetsController
- RevalidationService
- ProductsService
- seo.module.ts
- rss.xml/route.ts
- 🏢 AI Software Agency — Master Orchestration Protocol v3
@ -105,7 +105,7 @@
- dependencies
- CreateEBankCheckoutDto
- seed-products.ts
- useSettingsStore
- ClientLayout.tsx
- Reconciled Audit Roles & Assignments
- OrdersService
- auth.service.ts
@ -128,9 +128,9 @@
- Operational Rules & Boundaries
- Operational Rules & Boundaries
- Operational Rules & Boundaries
- orderService.ts
- lib/services/api.ts
- AppService
- Spinner.tsx
- MediaSelector.tsx
- Vazirmatn Changelog
- Vazirmatn Font فونت وزیرمتن
- Operational Rules & Boundaries
@ -138,13 +138,13 @@
- compilerOptions
- backend/README.md
- AdminController
- ConfirmModal.tsx
- auth.module.ts
- Repository Map
- validate_integrity.js
- admin-panel/package.json
- Sahel-Font
- HomeController
- LoginDto
- PaymentService
- Sahel-Font
- Role & Core Objective
- orchestrate.py
@ -160,14 +160,14 @@
- start-dev.js
- generate-openapi.js
- SafeImage.tsx
- AdminLoginDto
- Orders.tsx
- System Discovery
- torob.controller.ts
- Reports.tsx
- RegisterDto
- products.module.ts
- PetsService
- SmsLogQueryDto
- Product Requirement Document (PRD)
- @eslint/js
- AuthService
- CreateHealthLogDto
- exclude
- Baseline Command Plan & Reconciled Command History
- seo-backfill.ts
@ -193,7 +193,7 @@
- uploads/[...path]/route.ts
- app.e2e-spec.js
- نقشه جامع پوشش تست‌های سرتاسری (E2E Test Coverage Map) — پروژه Canina
- track/page.tsx
- CreateReminderDto
- API Contract Specification
- ⚙️ Backend Technical Review (05_dev_backend)
- 🎨 Frontend & Admin Panel Technical Review (06_dev_frontend)
@ -279,7 +279,7 @@
- @types/express
- @types/jest
- @types/multer
- @nestjs/swagger
- menu.module.ts
- Canina Pharma GmbH
- Pets Table
- Canina Iran Project Introduction
@ -323,22 +323,22 @@
- @types/react-dom
- @types/supertest
- eslint-plugin-react-refresh
- orders.service.ts
- videos.controller.ts
- WikiService
- ZibalCallbackQueryDto
- faq.module.ts
- bcrypt
- eslint-plugin-prettier
## God Nodes (most connected - your core abstractions)
1. `Roles()` - 108 edges
2. `PrismaService` - 89 edges
2. `PrismaService` - 91 edges
3. `useSettingsStore` - 63 edges
4. `SmsService` - 51 edges
5. `api` - 44 edges
5. `api` - 45 edges
6. `PaginationDto` - 41 edges
7. `AdminService` - 40 edges
8. `AdminController` - 39 edges
9. `Button()` - 39 edges
10. `PaymentController` - 38 edges
8. `Button()` - 40 edges
9. `AdminController` - 39 edges
10. `JwtAuthGuard` - 38 edges
## Surprising Connections (you probably didn't know these)
- `User Roles and Capabilities` --conceptually_related_to--> `User Profile Photo` [INFERRED]
@ -353,31 +353,31 @@
backend/src/orders/orders.controller.ts → frontend/admin-panel/src/types/admin.ts
## Import Cycles
- 3-file cycle: `frontend/application/lib/services/api.ts -> frontend/application/lib/store/userStore.ts -> frontend/application/lib/store/cartStore.ts -> frontend/application/lib/services/api.ts`
- 3-file cycle: `frontend/application/lib/services/api.ts -> frontend/application/lib/store/userStore.ts -> frontend/application/lib/services/authService.ts -> frontend/application/lib/services/api.ts`
- 3-file cycle: `frontend/application/lib/services/api.ts -> frontend/application/lib/store/userStore.ts -> frontend/application/lib/store/cartStore.ts -> frontend/application/lib/services/api.ts`
- 4-file cycle: `frontend/application/lib/services/api.ts -> frontend/application/lib/store/userStore.ts -> frontend/application/lib/store/cartStore.ts -> frontend/application/lib/services/orderService.ts -> frontend/application/lib/services/api.ts`
## Communities (332 total, 114 thin omitted)
## Communities (332 total, 117 thin omitted)
### Community 0 - "Roles"
Cohesion: 0.24
Nodes (13): Roles(), PaymentController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get (+5 more)
### Community 1 - "app.module.ts"
Cohesion: 0.07
Nodes (38): B2BModule, Module, BannersModule, Module, CmsModule, Module, SmsModule, Global (+30 more)
Cohesion: 0.08
Nodes (32): B2BModule, Module, BannersModule, Module, CmsModule, Module, SmsModule, Global (+24 more)
### Community 2 - "WikiController"
Cohesion: 0.21
Nodes (9): ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiTags, Controller, Get, Param, Query (+1 more)
### Community 2 - "ApiKeysService"
Cohesion: 0.07
Nodes (22): ApiKeysController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+14 more)
### Community 3 - "productService.ts"
Cohesion: 0.06
Nodes (40): buildTorobProductSpec(), buildTorobProductTitle(), dynamic, GET(), revalidate, dynamic, GET(), revalidate (+32 more)
Cohesion: 0.05
Nodes (47): buildTorobProductSpec(), buildTorobProductTitle(), dynamic, GET(), revalidate, CatalogClient(), generateMetadata(), dynamic (+39 more)
### Community 4 - "PetsController"
Cohesion: 0.15
Nodes (11): PetsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Delete, Get (+3 more)
Cohesion: 0.10
Nodes (14): PetsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Delete, Get (+6 more)
### Community 5 - "CmsController"
Cohesion: 0.09
@ -385,15 +385,15 @@ Nodes (24): CmsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controlle
### Community 6 - "tickets.controller.ts"
Cohesion: 0.09
Nodes (29): AdminUpdateTicketDto, CreateTicketDto, ReplyTicketDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString (+21 more)
Nodes (31): AdminUpdateTicketDto, CreateTicketDto, ReplyTicketDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString (+23 more)
### Community 7 - "SmsSettingsPage.tsx"
Cohesion: 0.20
Nodes (10): PatternItem, SmsConfigState, SmsEventDefinition, SmsEventVariable, SmsLogItem, SmsLogStats, SmsRule, SmsSettingsPage() (+2 more)
### Community 8 - "SmsService"
Cohesion: 0.05
Nodes (27): SmsEventDefinition, SmsService, Injectable, SmsLogQueryDto, ApiPropertyOptional, IsIn, IsNumber, IsOptional (+19 more)
Cohesion: 0.06
Nodes (21): SmsEventDefinition, SmsLogQuery, SmsService, Injectable, SettingsController, ApiBearerAuth, ApiOkResponse, ApiOperation (+13 more)
### Community 9 - "devDependencies"
Cohesion: 0.22
@ -404,24 +404,24 @@ Cohesion: 0.07
Nodes (30): CreateReviewDto, ApiProperty, IsInt, IsNotEmpty, IsOptional, IsString, Min, ApiProperty (+22 more)
### Community 11 - "UsersService"
Cohesion: 0.05
Nodes (42): DEFAULT_JWT_REFRESH_SECRET, DEFAULT_JWT_SECRET, getJwtSecret(), AuthModule, Module, JwtPayload, JwtStrategy, Injectable (+34 more)
Cohesion: 0.07
Nodes (32): AddressDto, ApiProperty, ApiPropertyOptional, IsBoolean, IsNotEmpty, IsOptional, IsString, ApiPropertyOptional (+24 more)
### Community 12 - "index.ts"
Cohesion: 0.06
Nodes (24): backend, frontend, playwright.config.ts, @playwright/test, tests/**/*.ts, authFile, test, compilerOptions (+16 more)
### Community 13 - "app-audit-verification.e2e-spec.js"
Cohesion: 0.06
Nodes (28): AppModule, Module, EnvironmentVariables, IsNotEmpty, IsOptional, IsString, validateEnv(), CustomHttpExceptionFilter (+20 more)
Cohesion: 0.07
Nodes (26): EnvironmentVariables, IsNotEmpty, IsOptional, IsString, validateEnv(), CustomHttpExceptionFilter, Catch, PrismaExceptionFilter (+18 more)
### Community 14 - "CheckoutPage.tsx"
Cohesion: 0.17
Nodes (19): AddressFormData, AddressModal(), AddressModalProps, normalizeDigits(), validateAddressForm(), BackButton(), BackButtonProps, CheckoutPage() (+11 more)
### Community 14 - "toPersian"
Cohesion: 0.11
Nodes (28): VerifyContent(), AddressFormData, AddressModal(), AddressModalProps, normalizeDigits(), validateAddressForm(), ArchivePage(), CheckoutPage() (+20 more)
### Community 15 - "src/services/api.ts"
Cohesion: 0.06
Nodes (38): Layout(), ProtectedRoute(), MenuGroup, Sidebar(), SidebarProps, SubMenuItem, LiveMonitoringSummary, SEARCHABLE_PAGES (+30 more)
Cohesion: 0.08
Nodes (31): Layout(), ProtectedRoute(), MenuGroup, Sidebar(), SidebarProps, SubMenuItem, LiveMonitoringSummary, SEARCHABLE_PAGES (+23 more)
### Community 16 - "DoctorQueryDto"
Cohesion: 0.09
@ -440,12 +440,12 @@ Cohesion: 0.35
Nodes (12): ApplyGlobalMarginsDto, BulkPriceAdjustmentDto, ApiProperty, ApiPropertyOptional, IsArray, IsBoolean, IsIn, IsNumber (+4 more)
### Community 20 - "CreateVideoDto"
Cohesion: 0.09
Nodes (24): CreateVideoDto, ApiProperty, ApiPropertyOptional, IsBoolean, IsNotEmpty, IsOptional, IsString, UpdateVideoDto (+16 more)
Cohesion: 0.07
Nodes (32): CreateVideoDto, ApiProperty, ApiPropertyOptional, IsBoolean, IsNotEmpty, IsOptional, IsString, UpdateVideoDto (+24 more)
### Community 21 - "HomeClient.tsx"
Cohesion: 0.10
Nodes (20): HomeClient(), HomeClientProps, BannerPlacement(), BannerPlacementProps, BlogPreviewSection(), FAQItem, FAQSection(), Hero() (+12 more)
### Community 21 - "useSettingsStore"
Cohesion: 0.09
Nodes (26): HomeClient(), HomeClientProps, B2BLandingClient(), BannerPlacement(), BannerPlacementProps, BlogPreviewSection(), BrandLogo(), BrandLogoProps (+18 more)
### Community 23 - "MenuService"
Cohesion: 0.12
@ -484,16 +484,16 @@ Cohesion: 0.06
Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Category, Completion Statement, Confidence, Dependencies (+23 more)
### Community 32 - "adminRoutes.tsx"
Cohesion: 0.07
Nodes (16): App(), Props, RouteErrorBoundary, State, HeroBanner, VetTestimonial, SslStatus, AdminRouteConfig (+8 more)
Cohesion: 0.06
Nodes (20): App(), Props, RouteErrorBoundary, State, HeroBanner, VetTestimonial, CategoryDist, DashboardData (+12 more)
### Community 33 - "WholesaleApplyDto"
Cohesion: 0.10
Nodes (20): ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, WholesaleApplyDto, ApiBearerAuth, ApiOperation (+12 more)
### Community 34 - "B2BService"
Cohesion: 0.13
Nodes (15): B2BController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+7 more)
Cohesion: 0.12
Nodes (16): B2BController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+8 more)
### Community 35 - "ContactService"
Cohesion: 0.13
@ -517,22 +517,22 @@ Nodes (16): CategoriesController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags
### Community 40 - "MediaController"
Cohesion: 0.11
Nodes (14): MediaController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
Nodes (16): MediaController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+8 more)
### Community 41 - "What You Must Do When Invoked"
Cohesion: 0.07
Nodes (26): For /graphify add and --watch, For /graphify query, For the commit hook and native CLAUDE.md integration, For --update and --cluster-only, /graphify, Honesty Rules, Interpreter guard for subcommands, Part A - Structural extraction for code files (+18 more)
### Community 42 - "SslController"
Cohesion: 0.13
Nodes (14): SslController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Post (+6 more)
Cohesion: 0.11
Nodes (15): SslController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Post (+7 more)
### Community 43 - "BannersService"
Cohesion: 0.13
Cohesion: 0.12
Nodes (15): BannersController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+7 more)
### Community 44 - "TestimonialsService"
Cohesion: 0.13
Cohesion: 0.12
Nodes (14): TestimonialsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
### Community 45 - "What You Must Do When Invoked"
@ -544,12 +544,12 @@ Cohesion: 0.27
Nodes (14): "coupons", "health_logs", "order_items", "orders", "pet_medical_conditions", "pets", "product_ingredients", "product_symptoms" (+6 more)
### Community 47 - "IngredientsService"
Cohesion: 0.13
Nodes (14): IngredientsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
Cohesion: 0.06
Nodes (27): IngredientsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+19 more)
### Community 48 - "toPersian"
Cohesion: 0.14
Nodes (24): VerifyContent(), AuthModal(), B2BPortal(), CartDrawer(), Header(), MENU_ICONS, MobileBottomNav(), SOLUTION_ITEMS (+16 more)
### Community 48 - "Button.tsx"
Cohesion: 0.10
Nodes (15): ButtonProps, ButtonSize, ButtonVariant, Spinner(), FAQ, SslStatus, FAQManager, PaymentGatewaysPage (+7 more)
### Community 49 - "devDependencies"
Cohesion: 0.11
@ -564,32 +564,32 @@ Cohesion: 0.07
Nodes (18): BlogsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+10 more)
### Community 52 - "PrescriptionsService"
Cohesion: 0.14
Cohesion: 0.13
Nodes (14): PrescriptionsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+6 more)
### Community 53 - "SmartAdvisorService"
Cohesion: 0.13
Cohesion: 0.12
Nodes (14): SmartAdvisorController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
### Community 54 - "Button.tsx"
Cohesion: 0.07
Nodes (24): ButtonProps, ButtonSize, ButtonVariant, maxWidthClasses, Modal(), ModalProps, ContactInfoItem, ContactSubmission (+16 more)
### Community 54 - "Modal.tsx"
Cohesion: 0.08
Nodes (19): maxWidthClasses, Modal(), ModalProps, ContactInfoItem, ContactSubmission, MENU_TABS, MenuItem, MenuType (+11 more)
### Community 55 - "UITexts.tsx"
Cohesion: 0.08
Nodes (23): ICON_REGISTRY, IconPickerModal(), IconPickerModalProps, ActiveStates, PRESET_BG_COLORS, PRESET_COLORS, RichTextEditor(), RichTextEditorProps (+15 more)
### Community 56 - "Orders.tsx"
Cohesion: 0.11
Nodes (15): Badge(), BadgeProps, BadgeVariant, variantStyles, Skeleton(), MonitoringStats, getPaymentMethodLabel(), Order (+7 more)
### Community 56 - "PrescriptionsManager.tsx"
Cohesion: 0.12
Nodes (14): Badge(), BadgeProps, BadgeVariant, variantStyles, Skeleton(), MonitoringStats, ProductItem, UserRecord (+6 more)
### Community 57 - "Role & Core Objective"
Cohesion: 0.09
Nodes (22): CREATE MODE — Normal Operation, Expected JSON Output Schema, File Scope Boundary, Forbidden Actions, MODE 1: REVIEW (called during Review Phase), MODE 2: CONTENT CREATION (standalone task from backlog), Operating Modes, Operational Rules & Boundaries (+14 more)
### Community 58 - "RedisService"
Cohesion: 0.09
Nodes (11): ApiExcludeController, Optional, MetricsController, Controller, Get, Res, RedisModule, Global (+3 more)
Cohesion: 0.07
Nodes (12): Optional, AppModule, Module, AuthService, Injectable, normalizeMobile(), RedisModule, Global (+4 more)
### Community 59 - "compilerOptions"
Cohesion: 0.06
@ -600,36 +600,36 @@ Cohesion: 0.23
Nodes (10): CreateUserDto, ApiProperty, ApiPropertyOptional, IsEmail, IsNotEmpty, IsNumber, IsOptional, IsString (+2 more)
### Community 61 - "ProductPage.tsx"
Cohesion: 0.12
Nodes (28): ArchiveProductCard(), ArchiveProductListItem(), CATEGORY_MAP, ICON_MAP, FeaturedProducts(), ProductCard(), ProductImageZoomModalProps, CalculatorState (+20 more)
Cohesion: 0.10
Nodes (38): ArchiveProductCard(), ArchiveProductListItem(), CATEGORY_MAP, ICON_MAP, FeaturedProducts(), ProductCard(), OrderSuccess(), OrderTracking() (+30 more)
### Community 62 - "ReportsController"
Cohesion: 0.14
Nodes (11): ReportsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Get, Query (+3 more)
### Community 62 - "admin.module.ts"
Cohesion: 0.08
Nodes (16): CategoryQuery, ReportsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Get (+8 more)
### Community 63 - "dependencies"
Cohesion: 0.09
Nodes (23): dependencies, bcrypt, bcryptjs, class-validator, compression, ioredis, @nestjs/common, @nestjs/passport (+15 more)
Nodes (23): dependencies, bcryptjs, class-validator, compression, ioredis, @nestjs/common, @nestjs/passport, @nestjs/platform-express (+15 more)
### Community 64 - "compilerOptions"
Cohesion: 0.10
Nodes (20): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, lib, module, moduleDetection, moduleResolution, noEmit (+12 more)
### Community 65 - "admin.service.ts"
Cohesion: 0.14
Nodes (15): CouponTargetInput, PaginationQuery, ProductDto, ApiPropertyOptional, IsArray, IsBoolean, IsNumber, IsOptional (+7 more)
Cohesion: 0.15
Nodes (14): CouponTargetInput, PaginationQuery, ProductDto, ApiPropertyOptional, IsArray, IsBoolean, IsNumber, IsOptional (+6 more)
### Community 66 - "AdminQueryDto"
Cohesion: 0.13
Nodes (8): ApiQuery, Get, Query, AdminProductQueryDto, AdminQueryDto, ApiPropertyOptional, IsOptional, IsString
### Community 67 - "pets/pets.controller.ts"
Cohesion: 0.13
Nodes (15): CreatePetDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional, IsString (+7 more)
Cohesion: 0.15
Nodes (13): CreatePetDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional, IsString (+5 more)
### Community 68 - "lib/services/api.ts"
Cohesion: 0.08
Nodes (25): BlogPost, ContactInfoItem, DeleteConfirmModal(), DeleteConfirmModalProps, OrderDetailsModal(), OrderDetailsModalProps, OrderRowSkeleton(), PetProfileSkeleton() (+17 more)
### Community 68 - "components/Skeleton.tsx"
Cohesion: 0.21
Nodes (5): OrderRowSkeleton(), PetProfileSkeleton(), ProductListSkeleton(), Skeleton(), SkeletonProps
### Community 69 - "Required Review Group Closures"
Cohesion: 0.10
@ -640,8 +640,8 @@ Cohesion: 0.08
Nodes (23): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, jsx, lib, module, moduleDetection, moduleResolution (+15 more)
### Community 71 - "getPageMetadata"
Cohesion: 0.08
Nodes (16): generateMetadata(), CatalogClient(), generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata() (+8 more)
Cohesion: 0.09
Nodes (14): generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata() (+6 more)
### Community 72 - "Operational Rules & Boundaries"
Cohesion: 0.11
@ -656,12 +656,12 @@ Cohesion: 0.13
Nodes (14): ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete, Get (+6 more)
### Community 75 - "PetsController"
Cohesion: 0.08
Nodes (32): CreateHealthLogDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, CreateReminderDto, ApiProperty (+24 more)
Cohesion: 0.14
Nodes (20): PetsController, ApiBadRequestResponse, ApiBearerAuth, ApiCreatedResponse, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiTags (+12 more)
### Community 76 - "RevalidationService"
Cohesion: 0.07
Nodes (24): RevalidationModule, Global, Module, RevalidationService, Injectable, GetProductsDto, ApiPropertyOptional, IsEnum (+16 more)
### Community 76 - "ProductsService"
Cohesion: 0.10
Nodes (17): GetProductsDto, ApiPropertyOptional, IsEnum, IsOptional, IsString, ProductsController, ApiOperation, ApiTags (+9 more)
### Community 77 - "seo.module.ts"
Cohesion: 0.16
@ -696,7 +696,7 @@ Cohesion: 0.12
Nodes (16): 1. Active Secret Scanning (All Modified Files), 2. Docker Container Verification (if Dockerfile exists), 3. CI/CD Basic Check (if `.github/workflows/` exists), 4. Failure Routing Protocol, 5. Forbidden Actions, ENFORCE MODE — Normal Operation, Expected JSON Output Schema, Operational Rules & Boundaries (+8 more)
### Community 85 - "AuthController"
Cohesion: 0.27
Cohesion: 0.23
Nodes (13): AuthController, ApiBadRequestResponse, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+5 more)
### Community 86 - "zibal.service.ts"
@ -715,21 +715,21 @@ Nodes (8): CreateEBankCheckoutDto, ApiProperty, ApiPropertyOptional, IsNotEmpty,
Cohesion: 0.17
Nodes (12): prisma, main(), prisma, determineSuitableFor(), findOrCreateCategory(), getProductImageUrl(), main(), parseSize() (+4 more)
### Community 90 - "useSettingsStore"
Cohesion: 0.08
Nodes (28): B2BPortal, CartDrawer, ClientLayout(), MobileBottomNav, PrescriptionUploadModal, metadata, ArchivePage(), B2BLandingClient() (+20 more)
### Community 90 - "ClientLayout.tsx"
Cohesion: 0.09
Nodes (28): AuthModal, B2BPortal, CartDrawer, ClientLayout(), MobileBottomNav, PrescriptionUploadModal, metadata, B2BPortal() (+20 more)
### Community 91 - "Reconciled Audit Roles & Assignments"
Cohesion: 0.12
Nodes (15): 10. Documentation Engineer, 1. Lead Architect / Orchestrator, 2. React / Vite Storefront Auditor, 3. NestJS Backend Auditor, 4. Admin Features Auditor, 5. Database & Data Integrity Auditor, 6. Security Auditor, 7. TypeScript & Code Quality Auditor (+7 more)
### Community 92 - "OrdersService"
Cohesion: 0.07
Nodes (29): CreateOrderDto, OrderItemDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional (+21 more)
Cohesion: 0.06
Nodes (35): CreateOrderDto, OrderItemDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional (+27 more)
### Community 93 - "auth.service.ts"
Cohesion: 0.22
Nodes (8): AdminLoginInput, LoginInput, RegisterInput, SendOtpDto, ApiProperty, IsNotEmpty, IsString, Matches
Cohesion: 0.06
Nodes (33): AdminLoginInput, LoginInput, RegisterInput, AdminLoginDto, ApiProperty, IsEmail, IsNotEmpty, IsString (+25 more)
### Community 94 - "Phase 3.1 — Human Review Preparation and Master Backlog Critique"
Cohesion: 0.13
@ -772,8 +772,8 @@ Cohesion: 0.15
Nodes (12): 10. `TASK-VERIFY-001`, 1. `TASK-SEC-001`, 2. `TASK-SEC-002`, 3. `TASK-SEC-003`, 4. `TASK-FIN-001`, 5. `TASK-BUILD-001`, 6. `TASK-AUTH-001`, 7. `TASK-FE-001` (+4 more)
### Community 104 - "Products.tsx"
Cohesion: 0.09
Nodes (25): Input, InputProps, formatPriceWithCommas(), PriceInput(), PriceInputProps, toEnglishDigits(), Coupon, CouponFormData (+17 more)
Cohesion: 0.10
Nodes (24): Input, InputProps, formatPriceWithCommas(), PriceInput(), PriceInputProps, toEnglishDigits(), Coupon, CouponFormData (+16 more)
### Community 105 - "Operational Rules & Boundaries"
Cohesion: 0.17
@ -784,12 +784,12 @@ Cohesion: 0.43
Nodes (7): InitiatePaymentDto, InitiateWalletTopupDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString
### Community 107 - "PaginationDto"
Cohesion: 0.05
Nodes (26): AdminModule, Module, CategoryQuery, MediaService, Injectable, PetQuery, PetsService, Injectable (+18 more)
Cohesion: 0.09
Nodes (13): BlogsModule, Module, BlogFilterDto, BlogsService, Injectable, PaginationDto, ApiPropertyOptional, IsEnum (+5 more)
### Community 108 - "PrismaService"
Cohesion: 0.06
Nodes (25): B2BWholesaleOrderItem, DEFAULT_SMS_RULES, SMS_EVENT_DEFINITIONS, SmsEventVariable, SmsRule, MeliPayamakPattern, MeliPayamakResponse, SendPatternSmsOptions (+17 more)
Cohesion: 0.05
Nodes (31): ApiExcludeController, MetricsController, Controller, Get, Res, RevalidationModule, Global, Module (+23 more)
### Community 109 - "1. Summary of Integrity Repairs Performed"
Cohesion: 0.17
@ -807,17 +807,17 @@ Nodes (10): 1. Mark Active Task as Complete, 2. Documentation Updates, 3. Dynami
Cohesion: 0.18
Nodes (10): 1. Pre-Deployment Checklist, 2. Build Verification, 3. Deployment Strategy (Based on Target), 4. Post-Deployment Health Check, 5. Forbidden Actions, Expected JSON Output Schema, Operational Rules & Boundaries, Required Output Artifacts (What files to write/update) (+2 more)
### Community 113 - "orderService.ts"
Cohesion: 0.20
Nodes (4): ApiErr, Order, OrderItem, OrderService
### Community 113 - "lib/services/api.ts"
Cohesion: 0.09
Nodes (16): ContactInfoItem, FAQItem, OrderDetailsModalProps, ProductReviewsProps, ReviewItem, api, ApiErrorPayload, baseURL (+8 more)
### Community 114 - "AppService"
Cohesion: 0.29
Nodes (5): AppController, Controller, Get, AppService, Injectable
### Community 115 - "Spinner.tsx"
Cohesion: 0.10
Nodes (22): ImagePreviewModal(), ImagePreviewModalProps, getFileType(), Media, MediaSelector(), MediaSelectorProps, Spinner(), Doctor (+14 more)
### Community 115 - "MediaSelector.tsx"
Cohesion: 0.06
Nodes (35): ConfirmModal(), ConfirmModalProps, ImagePreviewModal(), ImagePreviewModalProps, getFileType(), Media, MediaSelector(), MediaSelectorProps (+27 more)
### Community 116 - "Vazirmatn Changelog"
Cohesion: 0.18
@ -847,9 +847,9 @@ Nodes (9): Compile and run the project, Deployment, Description, License, Projec
Cohesion: 0.17
Nodes (12): AdminController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Param (+4 more)
### Community 123 - "ConfirmModal.tsx"
Cohesion: 0.10
Nodes (16): ConfirmModal(), ConfirmModalProps, Pagination(), PaginationProps, Category, Pet, DoctorOption, Video (+8 more)
### Community 123 - "auth.module.ts"
Cohesion: 0.15
Nodes (12): AdminModule, Module, DEFAULT_JWT_REFRESH_SECRET, DEFAULT_JWT_SECRET, getJwtSecret(), AuthModule, Module, JwtPayload (+4 more)
### Community 124 - "Repository Map"
Cohesion: 0.20
@ -871,10 +871,6 @@ Nodes (9): Arch Linux, Contributors, Install, Known problems for variable versio
Cohesion: 0.16
Nodes (10): HomeController, ApiOkResponse, ApiOperation, ApiTags, Controller, Get, HomeModule, Module (+2 more)
### Community 129 - "LoginDto"
Cohesion: 0.33
Nodes (5): LoginDto, ApiProperty, IsNotEmpty, IsString, MinLength
### Community 130 - "Sahel-Font"
Cohesion: 0.20
Nodes (9): Arch Linux, Contributors, Install, Known problems for variable version, License, Sahel-Font, To Do (variable), طریقه استفاده از نسخه متغیر variable (+1 more)
@ -892,8 +888,8 @@ Cohesion: 0.22
Nodes (8): author, description, license, name, prisma, seed, private, version
### Community 134 - "blog/page.tsx"
Cohesion: 0.38
Nodes (6): Blog(), generateMetadata(), getBlogs(), getCategories(), revalidate, BlogPage()
Cohesion: 0.47
Nodes (5): Blog(), generateMetadata(), getBlogs(), getCategories(), revalidate
### Community 135 - "graphify reference: extra exports and benchmark"
Cohesion: 0.22
@ -932,36 +928,32 @@ Cohesion: 0.25
Nodes (6): app_module_1, core_1, fs, path, swagger_1, yaml
### Community 144 - "SafeImage.tsx"
Cohesion: 0.11
Nodes (21): BlogPostClientProps, PLAYBACK_RATES, PodcastInlinePlayer(), PodcastInlinePlayerProps, PLAYBACK_RATES, PodcastPlayerModal(), PodcastPlayerModalProps, VideoModalPlayer (+13 more)
Cohesion: 0.08
Nodes (26): BackButton(), BackButtonProps, BlogPostClientProps, BlogPost, PLAYBACK_RATES, PodcastInlinePlayer(), PodcastInlinePlayerProps, PLAYBACK_RATES (+18 more)
### Community 145 - "AdminLoginDto"
Cohesion: 0.29
Nodes (6): AdminLoginDto, ApiProperty, IsEmail, IsNotEmpty, IsString, MinLength
### Community 145 - "Orders.tsx"
Cohesion: 0.22
Nodes (8): getPaymentMethodLabel(), Order, ORDER_STATUS_MAP, OrderItem, Orders(), PaymentTx, toPersianDigits(), Orders
### Community 146 - "System Discovery"
Cohesion: 0.25
Nodes (7): Active Core Applications, Current Architecture, Evidence-Based Status Matrix, Excluded / Non-Auditable Artifacts, Major Business Domains Discovered, System Discovery, Technical Architecture Summary
### Community 147 - "torob.controller.ts"
Cohesion: 0.22
Nodes (8): buildTorobProductSpec(), buildTorobProductTitle(), TorobController, ApiOperation, ApiTags, Controller, Get, Query
### Community 147 - "products.module.ts"
Cohesion: 0.18
Nodes (10): ProductsModule, Module, buildTorobProductSpec(), buildTorobProductTitle(), TorobController, ApiOperation, ApiTags, Controller (+2 more)
### Community 148 - "Reports.tsx"
Cohesion: 0.20
Nodes (7): BestSellerItem, CategoryDistItem, COLORS, CustomTooltipProps, ReportData, TopCouponItem, Reports
### Community 149 - "RegisterDto"
Cohesion: 0.22
Nodes (8): RegisterDto, ApiProperty, ApiPropertyOptional, IsEmail, IsNotEmpty, IsOptional, IsString, MinLength
### Community 149 - "SmsLogQueryDto"
Cohesion: 0.25
Nodes (7): SmsLogQueryDto, ApiPropertyOptional, IsIn, IsNumber, IsOptional, IsString, Type
### Community 150 - "Product Requirement Document (PRD)"
Cohesion: 0.29
Nodes (6): 1. Executive Vision, 2. Target Audience, 3. Functional Requirements, 4. Non-Functional Requirements (Performance, Security), 5. Epic / Feature Breakdown, Product Requirement Document (PRD)
### Community 152 - "AuthService"
Cohesion: 0.13
Nodes (9): AuthService, Injectable, ApiProperty, IsNotEmpty, IsString, Matches, VerifyOtpDto, normalizeMobile() (+1 more)
### Community 152 - "CreateHealthLogDto"
Cohesion: 0.29
Nodes (6): CreateHealthLogDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString
### Community 153 - "exclude"
Cohesion: 0.22
@ -1059,6 +1051,10 @@ Nodes (3): app_module_1, supertest_1, testing_1
Cohesion: 0.33
Nodes (5): نقشه جامع پوشش تست‌های سرتاسری (E2E Test Coverage Map) — پروژه Canina, ۱. معماری و نقش‌های کاربری (User Roles), ۲. ماتریس جریان‌ها و قابلیت‌های کاربرمحور (Feature & Flow Matrix), ۳. وضعیت پوشش نهایی سوئیت ۱۱گانه (Final 11 Test Suites Status), ۴. راهنمای نگهداری و افزودن فیچرهای جدید بدون شکستن تست‌ها (Developer Maintenance Guide)
### Community 179 - "CreateReminderDto"
Cohesion: 0.29
Nodes (6): CreateReminderDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString
### Community 180 - "API Contract Specification"
Cohesion: 0.50
Nodes (3): 1. OpenAPI 3.0 (Swagger) Specification, 2. Endpoint Definitions & Data Types, API Contract Specification
@ -1104,8 +1100,8 @@ Cohesion: 0.50
Nodes (3): Select, SelectOption, SelectProps
### Community 197 - "AuthModal.tsx"
Cohesion: 0.08
Nodes (19): AuthModal, LoginModal, AuthModalProps, extractOtpFromText(), otpCooldownExpiries, IMPORTANT: only depend on isOpen here — NOT subView. If we depend on subView,, extractOtpFromText(), LoginModal() (+11 more)
Cohesion: 0.09
Nodes (19): LoginModal, AuthModal(), AuthModalProps, extractOtpFromText(), otpCooldownExpiries, IMPORTANT: only depend on isOpen here — NOT subView. If we depend on subView,, extractOtpFromText(), LoginModal() (+11 more)
### Community 198 - "app/page.tsx"
Cohesion: 0.67
@ -1127,49 +1123,41 @@ Nodes (3): ADR-AUTH-001: Admin Panel Authentication Architecture & Token Managem
Cohesion: 0.67
Nodes (3): Shabnam Font README, Shabnam Font Sample, Vazir Font
### Community 294 - "ZibalService"
Cohesion: 0.09
Nodes (4): PaymentService, Injectable, Injectable, ZibalService
### Community 269 - "menu.module.ts"
Cohesion: 0.40
Nodes (3): MenuModule, Module, MenuType
### Community 298 - ".initiateOrderPayment"
Cohesion: 0.20
Nodes (10): ApiPropertyOptional, IsOptional, IsString, ZibalCallbackQueryDto, ApiBadRequestResponse, ApiOkResponse, Req, Res (+2 more)
Cohesion: 0.32
Nodes (6): ApiBadRequestResponse, ApiOkResponse, Req, Res, Headers, Ip
### Community 317 - "revalidate/route.ts"
Cohesion: 0.83
Nodes (3): GET(), handleRevalidate(), POST()
### Community 328 - "orders.service.ts"
Cohesion: 0.24
Nodes (6): IsNotEmpty, IsNumber, IsString, ValidateCouponDto, OrdersModule, Module
### Community 329 - "videos.controller.ts"
Cohesion: 0.22
Nodes (7): GetVideosQueryDto, ApiPropertyOptional, IsBoolean, IsOptional, Transform, Module, VideosModule
### Community 330 - "WikiService"
Cohesion: 0.27
Nodes (4): Module, WikiModule, Injectable, WikiService
### Community 328 - "ZibalCallbackQueryDto"
Cohesion: 0.40
Nodes (4): ApiPropertyOptional, IsOptional, IsString, ZibalCallbackQueryDto
## Knowledge Gaps
- **1356 isolated node(s):** `$schema`, `collection`, `sourceRoot`, `deleteOutDir`, `name` (+1351 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **114 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
- **117 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `Roles()` connect `Roles` to `WholesaleApplyDto`, `B2BService`, `ContactService`, `FaqService`, `CmsController`, `tickets.controller.ts`, `SmsService`, `SslController`, `BannersService`, `RevalidationService`, `CreateReviewDto`, `TestimonialsService`, `IngredientsService`, `JwtAuthGuard`, `PrescriptionsService`, `SmartAdvisorService`, `MenuService`?**
_High betweenness centrality (0.093) - this node is a cross-community bridge._
- **Why does `ApiResponse` connect `src/services/api.ts` to `HomeController`, `WikiController`, `BlogsController`, `PetsController`, `RevalidationService`, `UsersService`, `AuthController`, `OrdersService`?**
_High betweenness centrality (0.066) - this node is a cross-community bridge._
- **Why does `JwtAuthGuard` connect `JwtAuthGuard` to `pets/pets.controller.ts`, `CmsController`, `tickets.controller.ts`, `orders.service.ts`, `videos.controller.ts`, `PaginationDto`, `RevalidationService`, `PrismaService`, `DoctorQueryDto`, `admin.controller.ts`, `auth.service.ts`, `ReportsController`?**
_High betweenness centrality (0.030) - this node is a cross-community bridge._
- **Why does `ApiResponse` connect `src/services/api.ts` to `HomeController`, `ApiKeysService`, `BlogsController`, `PetsController`, `ProductsService`, `UsersService`, `IngredientsService`, `AuthController`, `OrdersService`?**
_High betweenness centrality (0.076) - this node is a cross-community bridge._
- **Why does `Roles()` connect `Roles` to `WholesaleApplyDto`, `B2BService`, `ContactService`, `FaqService`, `CmsController`, `tickets.controller.ts`, `SmsService`, `SslController`, `BannersService`, `ProductsService`, `CreateReviewDto`, `TestimonialsService`, `IngredientsService`, `JwtAuthGuard`, `PrescriptionsService`, `SmartAdvisorService`, `MenuService`?**
_High betweenness centrality (0.067) - this node is a cross-community bridge._
- **Why does `PrismaService` connect `PrismaService` to `HomeController`, `PaymentService`, `ApiKeysService`, `app.module.ts`, `PetsController`, `CmsController`, `tickets.controller.ts`, `SmsService`, `CreateReviewDto`, `UsersService`, `menu.module.ts`, `DoctorQueryDto`, `products.module.ts`, `PetsService`, `CreateVideoDto`, `MenuService`, `WholesaleApplyDto`, `B2BService`, `ContactService`, `FaqService`, `CategoriesController`, `MediaController`, `ZibalEBankService`, `BannersService`, `TestimonialsService`, `IngredientsService`, `PrescriptionsService`, `SmartAdvisorService`, `RedisService`, `admin.module.ts`, `admin.service.ts`, `pets/pets.controller.ts`, `faq.module.ts`, `ProductsService`, `seo.module.ts`, `zibal.service.ts`, `OrdersService`, `auth.service.ts`, `PaginationDto`?**
_High betweenness centrality (0.032) - this node is a cross-community bridge._
- **What connects `$schema`, `collection`, `sourceRoot` to the rest of the system?**
_1356 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `app.module.ts` be split into smaller, more focused modules?**
_Cohesion score 0.06516290726817042 - nodes in this community are weakly interconnected._
_Cohesion score 0.0797872340425532 - nodes in this community are weakly interconnected._
- **Should `ApiKeysService` be split into smaller, more focused modules?**
_Cohesion score 0.07422402159244265 - nodes in this community are weakly interconnected._
- **Should `productService.ts` be split into smaller, more focused modules?**
_Cohesion score 0.056189640035118525 - nodes in this community are weakly interconnected._
- **Should `CmsController` be split into smaller, more focused modules?**
_Cohesion score 0.0899854862119013 - nodes in this community are weakly interconnected._
_Cohesion score 0.04738721194417397 - nodes in this community are weakly interconnected._

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff