fix: Persian duplicate-value errors; admin panel favicon uses the project logo

- PrismaExceptionFilter returned "Unique constraint failed on: mobile" in
  English for a duplicate. It now answers in Persian, naming the field
  (mobile, national code, member code, order number), with a safe generic
  fallback. Covered by a unit test.
- The admin panel still shipped the old "NZ" favicon. favicon.png/.ico are now
  generated from the project logo (the same one the user apps use), in both
  public/ and dist/, and the links carry ?v=2 so browsers drop the cached icon.
- Rebuilt apps/api/release/app.js.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
s.esmaeili
2026-10-26 06:54:28 +03:30
co-authored by Claude Sonnet 5.5
parent 0c52adcd26
commit 600ce06d15
9 changed files with 74 additions and 18 deletions
@@ -0,0 +1,21 @@
import { uniqueViolationMessage } from './prisma-exception.filter';
describe('uniqueViolationMessage', () => {
it('names a duplicate mobile in Persian, whether Prisma sends columns or an index name', () => {
expect(uniqueViolationMessage(['mobile'])).toBe('این شماره موبایل قبلاً ثبت شده است');
expect(uniqueViolationMessage('Customer_mobile_key')).toBe('این شماره موبایل قبلاً ثبت شده است');
expect(uniqueViolationMessage('Admin_mobile_key')).toBe('این شماره موبایل قبلاً ثبت شده است');
});
it('names a duplicate national code', () => {
expect(uniqueViolationMessage(['nationalCode'])).toBe('این کد ملی قبلاً ثبت شده است');
});
it('never leaks the English Prisma wording, even for an unknown column or no target', () => {
for (const t of [['somethingElse'], undefined, null, []]) {
const msg = uniqueViolationMessage(t);
expect(msg).toBe('این مقدار قبلاً ثبت شده است و تکراری است');
expect(msg).not.toMatch(/[A-Za-z]/);
}
});
});
+22 -1
View File
@@ -2,6 +2,27 @@ import { ArgumentsHost, Catch, ExceptionFilter, HttpStatus, Logger } from '@nest
import { Response } from 'express';
import { Prisma } from '@prisma/client';
/**
* Persian, user-facing text for a unique-index violation. The panel shows
* `message` as-is, and the raw Prisma wording ("Unique constraint failed on:
* mobile") is meaningless to the operator.
*
* `target` is the list of columns on Postgres, or the index name when the
* driver only reports that — so both are matched by substring.
*/
export function uniqueViolationMessage(target: unknown): string {
const fields = (Array.isArray(target) ? target : [target])
.map((t) => String(t ?? ''))
.join(',')
.toLowerCase();
if (fields.includes('mobile')) return 'این شماره موبایل قبلاً ثبت شده است';
if (fields.includes('nationalcode')) return 'این کد ملی قبلاً ثبت شده است';
if (fields.includes('membercode')) return 'این کد عضویت قبلاً ثبت شده است';
if (fields.includes('ordernumber')) return 'این شماره سفارش قبلاً ثبت شده است';
return 'این مقدار قبلاً ثبت شده است و تکراری است';
}
/**
* Without this, an unmigrated table (P2021), a bad foreign key (P2003), etc.
* all surfaced as an opaque "Internal Server Error" with no indication of
@@ -44,7 +65,7 @@ export class PrismaExceptionFilter implements ExceptionFilter {
return response.status(HttpStatus.CONFLICT).json({
statusCode: HttpStatus.CONFLICT,
error: 'Conflict',
message: `Unique constraint failed on: ${(exception.meta?.target as string[] | undefined)?.join(', ')}`,
message: uniqueViolationMessage(exception.meta?.target),
});
case 'P2003': // foreign key constraint violation
return response.status(HttpStatus.BAD_REQUEST).json({