# Theme Management Module

A **completely new, self-contained** module for managing Themes, Theme Icons,
Theme Widgets and Widget Variants. It does **not** reuse, modify, or depend on
the legacy `dashboard_themes` / `theme_icons` / `widgets` implementation.

## Architecture (layered)

```
modules/theme-management/
├── constants/       enums + config (single source of truth)
├── models/          Mongoose schemas (new tm_* collections)
├── repositories/    pure data-access (no business rules)
├── services/        business logic (validation, uploads, counts, cascades)
├── validators/      request validation (field -> message maps)
├── controllers/     thin HTTP layer (parse -> service -> response)
├── helpers/         response envelope, AppError, asyncHandler, storage, logger, counts
└── routes/          the module router (mounted once in the app entrypoint)
```

Request flow: **route → verifyToken (existing) → controller → validator/service →
repository → model**. Errors bubble to the centralized handler in `asyncHandler`.

## Integration points (the ONLY existing files touched)

1. `font-keyboard-ios.js` — one `app.use(ThemeManagementRouter)` line.
2. React `src/App.js` — new route registrations (Tm* imports).
3. React `src/layout/Sidebar.jsx` — new "Theme Management" section.

## Data model

- **Identifier:** MongoDB `_id` is the single unique key for Themes — used
  internally and as the public API identifier. The Theme model keeps **no**
  separate auto-increment `id` and **no `uuid`**.
- **Sort order:** system-managed. Auto-assigned (`max + 1`) on create and
  recalculated on drag & drop reordering — never entered manually. Every listing
  (admin and public) is returned ordered by `sort_order ASC`.
- **Category:** the Theme `category` is chosen from the existing `sub_categories`
  under the "Theme" main category (read-only reference data), stored as its name.
- **Collections:** `tm_themes`, `tm_theme_icons`, `tm_theme_widgets`,
  `tm_widget_variants` (namespaced to avoid any collision).
- **Auto counts:** `icons_count` / `widgets_count` on a theme are recomputed
  automatically whenever related records change (`helpers/counts.js`).
  `wallpapers_count` has no owning collection here and defaults to 0 (editable).
- **Uploads:** reuse the shared `Helper/imageUpload.js` (DigitalOcean Spaces),
  stored under separate folders: `themes`, `theme-icons`, `theme-widgets`,
  `widget-variants`. Format + 10MB size validation is enforced.
- **Icon Sets:** a Theme Icon document is an **Icon Set** (icon pack), not a
  single icon. It has a `thumbnail_image` (the pack cover shown in the app),
  `premium`/`status`, and an embedded `icons[]` array — each `{ icon_name,
  deep_link_url, icon_image }` (icon image optional). Create/update save the
  thumbnail + all icon images in one request; on any failure every uploaded
  file is cleaned up. `icons_count` on a theme = the sum of icons across its sets.
- **Optional theme (like Widgets):** `theme_id` is optional. With a theme →
  **Theme Icon Set** (used only by that theme); without → **Global Icon Set**
  (available across the app). Same behaviour as Theme Widgets.
- **Default icons (seed only):** the application's standard default icons are
  NOT managed by this module — they are seeded into `tm_default_icons` via
  `npm run seed` (see [Seed](#seed) below). The Theme Icons module manages only
  custom icon sets.

## API (all POST, namespaced under `/theme-management`, JWT-protected)

### Themes
`/themes` · `/themes/list` · `/themes/options` · `/themes/categories` ·
`/themes/detail/:id` · `/themes/update/:id` · `/themes/status/:id` ·
`/themes/sort` · `/themes/delete/:id`

Filters (list body): `search`, `theme_type` (home|lock), `premium`, `status`,
`category`, plus `page` / `limit`.

Color tab (lock-screen themes only): `lock_screen_color` (hex string, e.g.
`#000000`) and `color_preview_image` (file). Both are rejected with a 422
when `theme_type` is not `lock`. Returned as-is in list + detail responses.

### Theme Icons (Icon Sets)
`/icons` · `/icons/bulk-import` · `/icons/list` · `/icons/detail/:id` ·
`/icons/update/:id` · `/icons/status/:id` · `/icons/premium/:id` · `/icons/sort` ·
`/icons/bulk-delete` · `/icons/delete/:id`

Create/update body (multipart): `theme_id`, `premium`, `status`,
`thumbnail_image` (file), and `icons` (JSON array of
`{ _id?, icon_name, deep_link_url, image_field?, icon_image? }`). Each new/replacement
icon image is uploaded under the field named by its `image_field` (e.g. `icon_image_0`);
alternatively a new icon may carry a pre-hosted `icon_image` URL (used by bulk import).
On update, omitted icons are removed (their images cleaned up).

**Bulk import** — `POST /icons/bulk-import` (multipart, field `zip_file`): extracts a
ZIP of PNG icons, uploads each valid PNG to storage and returns
`{ imported: [{ icon_name, deep_link_url: null, icon_image }], skipped: [{ name, reason }], summary }`
**without** creating an Icon Set. Icon names are derived from file names; non-PNG,
hidden (`.DS_Store`, `__MACOSX`, dotfiles) and duplicate entries are skipped. The admin
reviews/edits the returned icons and then saves via `/icons`. Partial uploads roll back
on failure.

### Theme Widgets
`/widgets` · `/widgets/list` · `/widgets/detail/:id` · `/widgets/update/:id` ·
`/widgets/status/:id` · `/widgets/sort` · `/widgets/delete/:id`

### Widget Variants
`/variants` · `/variants/sort` (bulk) · `/variants/update/:id` ·
`/variants/status/:id` · `/variants/delete/:id`

Variant rule: at most one variant per size (`small` / `medium` / `large`) per
widget — enforced by a unique index **and** the service layer.

Variants also accept an optional `shape` (`circular` | `rectangular` |
`inline`) describing the presentation style, mainly used by lock-screen
widgets.

### Public (user) APIs — unauthenticated, read-only

Active records only, **cursor-based pagination** (cursor = last `_id`, ordered
`sort_order ASC, _id ASC`), envelope `{ success, message, data, pagination }`
where `pagination = { limit, next_cursor, has_next_page }`. Query params:
`cursor`, `limit` (default 20, max 100), and for Icons/Widgets/Wallpapers
`theme_id` (optional) + `type`. Scope: `theme_id` → that theme's records;
`type=theme` → theme-assigned only; `type=global` → Global only (`theme_id =
null`); no scope → **all** active records. Additionally, Themes accept
`theme_type` and Widgets accept `widget_screen` (both `home | lock_screen`;
the stored `lock` value is also accepted) to filter by screen type. An
unrecognized value returns `400`.

- `GET /theme-management/app/themes` (`?theme_type=home|lock_screen`) · `GET /theme-management/app/themes/:id`
- `GET /theme-management/app/icons` · `GET /theme-management/app/icons/:id`
- `GET /theme-management/app/widgets` (`?widget_screen=home|lock_screen`) · `GET /theme-management/app/widgets/:id`
  — each widget in **both** the list and the detail response includes a
  `variants` array (its active size variants, sorted `sort_order ASC`), so
  the app can render the small/medium/large size picker straight from the
  list without an extra per-widget call.
- `GET /theme-management/app/wallpapers` · `GET /theme-management/app/wallpapers/:id`
  (wallpapers are read from the existing `wallpapers` collection). Wallpapers may be
  Global (`theme_id = null`) or Theme-scoped. Each item exposes explicit
  `preview_image` / `preview_image_ipad` and `full_image` / `full_image_ipad` (plus the
  original `wallpaper_url*` fields for backward compatibility) and `resolution` when set.

A ready-to-import Postman collection covering all of the above lives in
`postman/ThemeManagement-UserAPIs.postman_collection.json`.

## Seed

Default icons (the standard app icons) are seed data — not managed via the
admin panel. Seed / refresh them with:

```
npm run seed          # → node modules/theme-management/scripts/seed.js
```

- Data lives in `scripts/defaultIcons.data.json` (`icon_name` + `deep_link_url`
  only for now; images added later).
- Idempotent: upserts by the unique `icon_name`, so re-running never duplicates.
- Target collection: `tm_default_icons` (self-contained; the Theme Icons module
  does not read or write it).

## Response envelope

```json
{ "status": true, "response_code": 200, "response_message": "...", "data": ..., "meta": { "page": 1, "limit": 10, "total": 42, "totalPages": 5 } }
```
Validation failures return `response_code: 422` with an `errors` field map.
