style: auto-format code via Prettier [skip ci]

This commit is contained in:
Gitea Actions
2026-02-18 10:48:03 +05:00
parent ae0bb9e04d
commit 0656ab3ae7
80 changed files with 872 additions and 401 deletions

View File

@@ -56,7 +56,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -90,7 +90,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -114,7 +114,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -138,7 +138,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -161,7 +161,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -189,7 +189,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -211,7 +211,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -234,7 +234,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -259,7 +259,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -284,7 +284,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -307,7 +307,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -330,7 +330,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -355,7 +355,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -379,7 +379,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -425,7 +425,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -448,7 +448,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -476,7 +476,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -502,7 +502,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -529,7 +529,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -555,7 +555,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -579,7 +579,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -612,7 +612,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -637,7 +637,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -656,7 +656,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -681,7 +681,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -705,7 +705,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -757,7 +757,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Measurements**: **********************\_\_\_**********************
**Measurements**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -765,7 +765,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
### Test 8.1: Chrome/Edge
**Browser Version**: ******\_\_\_******
**Browser Version**: **\*\***\_\_\_**\*\***
**Tests to Run**:
@@ -775,13 +775,13 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
### Test 8.2: Firefox
**Browser Version**: ******\_\_\_******
**Browser Version**: **\*\***\_\_\_**\*\***
**Tests to Run**:
@@ -791,13 +791,13 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
### Test 8.3: Safari (macOS/iOS)
**Browser Version**: ******\_\_\_******
**Browser Version**: **\*\***\_\_\_**\*\***
**Tests to Run**:
@@ -807,7 +807,7 @@ podman exec -it flyer-crawler-dev npm run dev:container
**Pass/Fail**: [ ]
**Notes**: **********************\_\_\_**********************
**Notes**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
---
@@ -849,8 +849,8 @@ podman exec -it flyer-crawler-dev npm run dev:container
## Sign-Off
**Tester Name**: **********************\_\_\_**********************
**Date Completed**: **********************\_\_\_**********************
**Tester Name**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
**Date Completed**: ****\*\*****\*\*****\*\*****\_\_\_****\*\*****\*\*****\*\*****
**Overall Status**: [ ] PASS [ ] PASS WITH ISSUES [ ] FAIL
**Ready for Production**: [ ] YES [ ] NO [ ] WITH FIXES

View File

@@ -208,7 +208,7 @@ Press F12 or Ctrl+Shift+I
**Result**: [ ] PASS [ ] FAIL
**Errors found**: ******************\_\_\_******************
**Errors found**: **\*\*\*\***\*\***\*\*\*\***\_\_\_**\*\*\*\***\*\***\*\*\*\***
---
@@ -224,7 +224,7 @@ Check for:
**Result**: [ ] PASS [ ] FAIL
**Issues found**: ******************\_\_\_******************
**Issues found**: **\*\*\*\***\*\***\*\*\*\***\_\_\_**\*\*\*\***\*\***\*\*\*\***
---
@@ -272,4 +272,4 @@ Check for:
2. ***
3. ***
**Sign-off**: ********\_\_\_******** **Date**: ****\_\_\_****
**Sign-off**: **\*\*\*\***\_\_\_**\*\*\*\*** **Date**: \***\*\_\_\_\*\***

View File

@@ -39,15 +39,15 @@ All cache operations are fail-safe - cache failures do not break the application
Different data types use different TTL values based on volatility:
| Data Type | TTL | Rationale |
| ------------------- | --------- | -------------------------------------- |
| Brands/Stores | 1 hour | Rarely changes, safe to cache longer |
| Flyer lists | 5 minutes | Changes when new flyers are added |
| Individual flyers | 10 minutes| Stable once created |
| Flyer items | 10 minutes| Stable once created |
| Statistics | 5 minutes | Can be slightly stale |
| Frequent sales | 15 minutes| Aggregated data, updated periodically |
| Categories | 1 hour | Rarely changes |
| Data Type | TTL | Rationale |
| ----------------- | ---------- | ------------------------------------- |
| Brands/Stores | 1 hour | Rarely changes, safe to cache longer |
| Flyer lists | 5 minutes | Changes when new flyers are added |
| Individual flyers | 10 minutes | Stable once created |
| Flyer items | 10 minutes | Stable once created |
| Statistics | 5 minutes | Can be slightly stale |
| Frequent sales | 15 minutes | Aggregated data, updated periodically |
| Categories | 1 hour | Rarely changes |
### Cache Key Strategy
@@ -64,11 +64,11 @@ Cache keys follow a consistent prefix pattern for pattern-based invalidation:
The following repository methods implement server-side caching:
| Method | Cache Key Pattern | TTL |
| ------ | ----------------- | --- |
| `FlyerRepository.getAllBrands()` | `cache:brands` | 1 hour |
| `FlyerRepository.getFlyers()` | `cache:flyers:{limit}:{offset}` | 5 minutes |
| `FlyerRepository.getFlyerItems()` | `cache:flyer-items:{flyerId}` | 10 minutes |
| Method | Cache Key Pattern | TTL |
| --------------------------------- | ------------------------------- | ---------- |
| `FlyerRepository.getAllBrands()` | `cache:brands` | 1 hour |
| `FlyerRepository.getFlyers()` | `cache:flyers:{limit}:{offset}` | 5 minutes |
| `FlyerRepository.getFlyerItems()` | `cache:flyer-items:{flyerId}` | 10 minutes |
### Cache Invalidation
@@ -86,14 +86,14 @@ The following repository methods implement server-side caching:
TanStack React Query provides client-side caching with configurable stale times:
| Query Type | Stale Time |
| ----------------- | ----------- |
| Categories | 1 hour |
| Master Items | 10 minutes |
| Flyer Items | 5 minutes |
| Flyers | 2 minutes |
| Shopping Lists | 1 minute |
| Activity Log | 30 seconds |
| Query Type | Stale Time |
| -------------- | ---------- |
| Categories | 1 hour |
| Master Items | 10 minutes |
| Flyer Items | 5 minutes |
| Flyers | 2 minutes |
| Shopping Lists | 1 minute |
| Activity Log | 30 seconds |
### Multi-Layer Cache Architecture

View File

@@ -80,13 +80,13 @@ src/
**Common Utility Patterns**:
| Pattern | Classes |
| ------- | ------- |
| Card container | `bg-white dark:bg-gray-800 rounded-lg shadow-md p-6` |
| Primary button | `bg-brand-primary hover:bg-brand-dark text-white rounded-lg px-4 py-2` |
| Secondary button | `bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200` |
| Input field | `border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2` |
| Focus ring | `focus:outline-none focus:ring-2 focus:ring-brand-primary` |
| Pattern | Classes |
| ---------------- | ---------------------------------------------------------------------- |
| Card container | `bg-white dark:bg-gray-800 rounded-lg shadow-md p-6` |
| Primary button | `bg-brand-primary hover:bg-brand-dark text-white rounded-lg px-4 py-2` |
| Secondary button | `bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200` |
| Input field | `border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2` |
| Focus ring | `focus:outline-none focus:ring-2 focus:ring-brand-primary` |
### Color System
@@ -187,13 +187,13 @@ export const CheckCircleIcon: React.FC<IconProps> = ({ title, ...props }) => (
**Context Providers** (see ADR-005):
| Provider | Purpose |
| -------- | ------- |
| `AuthProvider` | Authentication state |
| `ModalProvider` | Modal open/close state |
| `FlyersProvider` | Flyer data |
| `MasterItemsProvider` | Grocery items |
| `UserDataProvider` | User-specific data |
| Provider | Purpose |
| --------------------- | ---------------------- |
| `AuthProvider` | Authentication state |
| `ModalProvider` | Modal open/close state |
| `FlyersProvider` | Flyer data |
| `MasterItemsProvider` | Grocery items |
| `UserDataProvider` | User-specific data |
**Provider Hierarchy** in `AppProviders.tsx`:

View File

@@ -45,15 +45,15 @@ Using **helmet v8.x** configured in `server.ts` as the first middleware after ap
**Security Headers Applied**:
| Header | Configuration | Purpose |
| ------ | ------------- | ------- |
| Content-Security-Policy | Custom directives | Prevents XSS, code injection |
| Strict-Transport-Security | 1 year, includeSubDomains, preload | Forces HTTPS connections |
| X-Content-Type-Options | nosniff | Prevents MIME type sniffing |
| X-Frame-Options | DENY | Prevents clickjacking |
| X-XSS-Protection | 0 (disabled) | Deprecated, CSP preferred |
| Referrer-Policy | strict-origin-when-cross-origin | Controls referrer information |
| Cross-Origin-Resource-Policy | cross-origin | Allows external resource loading |
| Header | Configuration | Purpose |
| ---------------------------- | ---------------------------------- | -------------------------------- |
| Content-Security-Policy | Custom directives | Prevents XSS, code injection |
| Strict-Transport-Security | 1 year, includeSubDomains, preload | Forces HTTPS connections |
| X-Content-Type-Options | nosniff | Prevents MIME type sniffing |
| X-Frame-Options | DENY | Prevents clickjacking |
| X-XSS-Protection | 0 (disabled) | Deprecated, CSP preferred |
| Referrer-Policy | strict-origin-when-cross-origin | Controls referrer information |
| Cross-Origin-Resource-Policy | cross-origin | Allows external resource loading |
**Content Security Policy Directives**:
@@ -87,35 +87,35 @@ Using **express-rate-limit v8.2.1** with a centralized configuration in `src/con
```typescript
const standardConfig = {
standardHeaders: true, // Sends RateLimit-* headers
standardHeaders: true, // Sends RateLimit-* headers
legacyHeaders: false,
skip: shouldSkipRateLimit, // Disabled in test environment
skip: shouldSkipRateLimit, // Disabled in test environment
};
```
**Rate Limiters by Category**:
| Category | Limiter | Window | Max Requests |
| -------- | ------- | ------ | ------------ |
| **Authentication** | loginLimiter | 15 min | 5 |
| | registerLimiter | 1 hour | 5 |
| | forgotPasswordLimiter | 15 min | 5 |
| | resetPasswordLimiter | 15 min | 10 |
| | refreshTokenLimiter | 15 min | 20 |
| | logoutLimiter | 15 min | 10 |
| **Public/User Read** | publicReadLimiter | 15 min | 100 |
| | userReadLimiter | 15 min | 100 |
| | userUpdateLimiter | 15 min | 100 |
| **Sensitive Operations** | userSensitiveUpdateLimiter | 1 hour | 5 |
| | adminTriggerLimiter | 15 min | 30 |
| **AI/Costly** | aiGenerationLimiter | 15 min | 20 |
| | geocodeLimiter | 1 hour | 100 |
| | priceHistoryLimiter | 15 min | 50 |
| **Uploads** | adminUploadLimiter | 15 min | 20 |
| | aiUploadLimiter | 15 min | 10 |
| | batchLimiter | 15 min | 50 |
| **Tracking** | trackingLimiter | 15 min | 200 |
| | reactionToggleLimiter | 15 min | 150 |
| Category | Limiter | Window | Max Requests |
| ------------------------ | -------------------------- | ------ | ------------ |
| **Authentication** | loginLimiter | 15 min | 5 |
| | registerLimiter | 1 hour | 5 |
| | forgotPasswordLimiter | 15 min | 5 |
| | resetPasswordLimiter | 15 min | 10 |
| | refreshTokenLimiter | 15 min | 20 |
| | logoutLimiter | 15 min | 10 |
| **Public/User Read** | publicReadLimiter | 15 min | 100 |
| | userReadLimiter | 15 min | 100 |
| | userUpdateLimiter | 15 min | 100 |
| **Sensitive Operations** | userSensitiveUpdateLimiter | 1 hour | 5 |
| | adminTriggerLimiter | 15 min | 30 |
| **AI/Costly** | aiGenerationLimiter | 15 min | 20 |
| | geocodeLimiter | 1 hour | 100 |
| | priceHistoryLimiter | 15 min | 50 |
| **Uploads** | adminUploadLimiter | 15 min | 20 |
| | aiUploadLimiter | 15 min | 10 |
| | batchLimiter | 15 min | 50 |
| **Tracking** | trackingLimiter | 15 min | 200 |
| | reactionToggleLimiter | 15 min | 150 |
**Test Environment Handling**:
@@ -140,7 +140,7 @@ sanitizeFilename(filename: string): string
**Multer Configuration** (`src/middleware/multer.middleware.ts`):
- MIME type validation via `imageFileFilter` (only image/* allowed)
- MIME type validation via `imageFileFilter` (only image/\* allowed)
- File size limits (2MB for logos, configurable per upload type)
- Unique filenames using timestamps + random suffixes
- User-scoped storage paths
@@ -203,10 +203,12 @@ Per-request structured logging (ADR-004):
```typescript
import cors from 'cors';
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || 'http://localhost:3000',
credentials: true,
}));
app.use(
cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || 'http://localhost:3000',
credentials: true,
}),
);
```
2. **Redis-backed rate limiting**: For distributed deployments, use `rate-limit-redis` store

View File

@@ -16,12 +16,12 @@ We will adopt a hybrid naming convention strategy to explicitly distinguish betw
1. **Database and AI Types (`snake_case`)**:
Interfaces, Type definitions, and Zod schemas that represent raw database rows or direct AI responses **MUST** use `snake_case`.
- *Examples*: `AiFlyerDataSchema`, `ExtractedFlyerItemSchema`, `FlyerInsert`.
- *Reasoning*: This avoids unnecessary mapping layers when inserting data into the database or parsing AI output. It serves as a visual cue that the data is "raw", "external", or destined for persistence.
- _Examples_: `AiFlyerDataSchema`, `ExtractedFlyerItemSchema`, `FlyerInsert`.
- _Reasoning_: This avoids unnecessary mapping layers when inserting data into the database or parsing AI output. It serves as a visual cue that the data is "raw", "external", or destined for persistence.
2. **Internal Application Logic (`camelCase`)**:
Variables, function arguments, and processed data structures used within the application logic (Service layer, UI components, utility functions) **MUST** use `camelCase`.
- *Reasoning*: This adheres to standard JavaScript/TypeScript practices and maintains consistency with the rest of the ecosystem (React, etc.).
- _Reasoning_: This adheres to standard JavaScript/TypeScript practices and maintains consistency with the rest of the ecosystem (React, etc.).
3. **Boundary Handling**:
- For background jobs that primarily move data from AI to DB, preserving `snake_case` is preferred to minimize transformation logic.

View File

@@ -486,9 +486,9 @@ Attach screenshots for:
## 🔐 Sign-Off
**Tester Name**: ******\*\*\*\*******\_\_\_******\*\*\*\*******
**Tester Name**: **\*\***\*\*\*\***\*\***\_\_\_**\*\***\*\*\*\***\*\***
**Date/Time Completed**: ****\*\*\*\*****\_\_\_****\*\*\*\*****
**Date/Time Completed**: \***\*\*\*\*\*\*\***\_\_\_\***\*\*\*\*\*\*\***
**Total Testing Time**: **\_\_** minutes