Initial commit

This commit is contained in:
Igor Martyniuk
2026-06-25 15:53:49 +03:00
commit 1bb0b7e68a
1517 changed files with 218469 additions and 0 deletions
+74
View File
@@ -0,0 +1,74 @@
# CRUX Documentation
This is documentation home for the CRUX monorepo. Use it for system-level architecture, application guides, shared package references, and core module documentation.
## Monorepo Map
| Area | Source | Documentation |
| --------------- | --------------------------------------------------- | -------------------------------------------- |
| Backend API | `backend/` | Core module docs and backend-specific guides |
| Admin frontend | `apps/admin/` | [Admin app docs](./apps/admin/Readme.md) |
| Shared packages | `packages/` | [Packages index](./packages/Readme.md) |
| Devtools | `apps/admin/src/devtools/` | [Devtools docs](./devtools/Readme.md) |
| Core modules | `backend/app/base_modules/`, `apps/admin/src/core/` | [Core modules](#core-modules) |
| Future apps | `apps/*` | Add an app page under `docs/apps/` |
## App Architecture
| Architecture | Scope | Documentation |
| -------------- | ------------------------------------------------- | --------------------------------------------------------------------------- |
| React frontend | Frontend apps and frontend modules under `apps/*` | [React frontend architecture](./apps-architecture/react-frontend/Readme.md) |
| Backend | AdonisJS backend apps and backend modules | [Backend architecture](./apps-architecture/backend/Readme.md) |
## Core Modules
Core modules are reusable admin panel areas that may have both backend and frontend implementations.
| Module | Backend | Frontend | Documentation |
| ---------------- | --------------------------------------- | -------------------------------------- | ----------------------------------------------------- |
| Access Control | Admin, Role, Permission base modules | `apps/admin/src/core/AccessControl/` | [Overview](./core-modules/access-control/Readme.md) |
| Account Settings | Admin profile endpoints | `apps/admin/src/core/AccountSettings/` | [Overview](./core-modules/account-settings/Readme.md) |
| Auth | `backend/app/base_modules/Auth/` | `apps/admin/src/core/Auth/` | [Overview](./core-modules/auth/Readme.md) |
| Fallback Screens | Frontend-only | `apps/admin/src/core/FallbackScreens/` | [Overview](./core-modules/fallback-screens/Readme.md) |
| File Manager | `backend/app/base_modules/FileManager/` | `apps/admin/src/core/FileManager/` | [Overview](./core-modules/file-manager/Readme.md) |
| Permission | Permission service and policies | `apps/admin/src/core/Permission/` | [Overview](./core-modules/permission/Readme.md) |
Add a new page when the information has a stable owner. Keep temporary notes in issue trackers or task files until they are worth maintaining.
## How To Write Module Docs
Each module should answer these questions:
1. What user or system problem does the module solve?
2. Where is the backend code?
3. Where is the frontend code?
4. What are the main flows from route to service to UI?
5. What data contracts cross app boundaries?
6. Which files should a developer edit for common changes?
7. What extension points are supported?
8. What behavior should not be changed without a migration plan?
Prefer short sections, source paths, and concrete examples over long prose.
## Documentation Rules
- Use `Readme.md` as the index for each folder.
- Use relative links between documentation pages.
- Reference source files with repository-relative paths.
- Keep backend and frontend details separate when their ownership differs.
- Document current behavior first, then list planned work under a `Planned Work` section.
- Update docs in the same change when modifying routes, DTOs, permissions, storage contracts, package APIs, or module state ownership.
## Local Development
Common root scripts:
| Command | Purpose |
| ----------------------- | ------------------------------------- |
| `npm run dev:backend` | Start the AdonisJS backend. |
| `npm run dev:admin-app` | Start the React admin app. |
| `npm run dev:all` | Start backend and admin app together. |
| `npm run build:backend` | Build the backend. |
| `npm run build:admin` | Build the admin app. |
For product overview, setup details, and environment variables, see the project root `Readme.md`.
+61
View File
@@ -0,0 +1,61 @@
# Backend Architecture
This guide is a short backend architecture reference for CRUX backend apps.
The current backend is based on AdonisJS v6. Use the official AdonisJS folder structure as the default baseline:
https://v6-docs.adonisjs.com/guides/getting-started/folder-structure
## Main Idea
Keep the backend close to the standard AdonisJS structure. Add project-specific folders only when they make ownership clearer.
## Default Folders
| Folder | Purpose |
| ----------- | -------------------------------------------------------------------------------------------- |
| `app/` | Application code: controllers, services, middleware, policies, validators, and domain logic. |
| `start/` | Boot lifecycle files such as routes, kernel setup, env loading, and event registration. |
| `config/` | Runtime configuration for AdonisJS and installed packages. |
| `database/` | Migrations and seeders. |
| `bin/` | Entrypoints for server, console, and tests. |
| `public/` | Static files and generated public assets. |
| `tests/` | Backend tests. |
## Basic Flow
```txt
start/routes.ts
-> controller
-> validator / policy
-> service
-> model / database / external service
```
Controllers should handle HTTP concerns. Services should hold business logic. Validators should own request validation. Policies and abilities should own authorization.
## Module Folders
If a backend app has reusable or generated modules, keep each module's files together when that improves maintainability.
Example:
```txt
app/base_modules/ModuleName/
controller/
services/
validator/
policy/
routes.ts
```
This is an app convention, not an AdonisJS requirement. Prefer the default AdonisJS structure unless a module boundary is useful.
## Rules
- Follow AdonisJS conventions first.
- Keep HTTP logic out of services.
- Keep business logic out of controllers.
- Validate request input before calling services.
- Use policies or abilities for protected operations.
- Document module-specific architecture in module docs, not in this general guide.
@@ -0,0 +1,392 @@
# CRUX React Frontend Architecture
This guide describes the recommended React frontend architecture for apps in the CRUX monorepo.
Its main goal is to make frontend development easier, more structured, and consistent across teams, so that every developer can write and understand each other's code quickly.
## Table of Contents
- [Base Concepts](#base-concepts)
- [Disclaimer](#disclaimer)
- [Core Principles](#core-principles)
- [Recommended Layering](#recommended-layering)
- [Architecture Scope](#architecture-scope)
- [Layer Dependency Rules](#layer-dependency-rules)
- [Entities Layer](#1-entities-layer)
- [Data Layer (Data Access Layer)](#2-data-layer-data-access-layer)
- [Purpose](#purpose)
- [Responsibilities](#responsibilities-2)
- [Rules and Guidelines](#rules-and-guidelines)
- [Infrastructure Layer](#infrastructure-layer)
- [Reference Data Layer](#reference-data-layer)
- [Providers Layer](#3-providers-layer)
- [Pages Layer](#4-pages-layer)
- [View Layer](#5-view-layer)
- [view/components/](#viewcomponents)
- [view/forms/](#viewforms)
- [view/widgets/](#viewwidgets)
- [Shared Layer](#6-shared-layer)
- [Responsibilities](#responsibilities-4)
- [Rules](#rules-2)
- [Full documentation](#full-documentation)
## Base Concepts
- **Purpose:** Provide a shared frontend foundation with predefined packages, utilities, and patterns for React apps and frontend modules in this monorepo.
- **Goals:**
- Reduce boilerplate code.
- Standardize folder structure, file naming, and coding practices in the company context.
- Assist in onboarding of new developers.
- **Included:**
- Preconfigured `React` + `TypeScript` setup.
- Common utilities and helper functions.
- Shared UI components, hooks, and state management helpers.
- Integrated i18n, form validation, and API utilities.
## Disclaimer
This architecture guide is intended as a **recommendation**, not a strict rulebook. **Feel free to adapt and modify it based on your team’s needs and project context**.
Key points to keep in mind:
1. **Maintainability comes first**.
The main goal of this structure is to help someone new to the project quickly understand where to make changes.
It’s about finding the right place to touch the code now, rather than strictly following a pattern for its own sake.
**Ultimately, the main goal is to make development easier for everyone**
2. **Naming debates are inevitable**.
Any architecture guide may spark discussions about file, folder, or function naming. Use this guide as a reference, but remember that practicality and clarity are more important than strict adherence.
3. **Flexibility is allowed**.
- You can adjust layers, module structure, or naming conventions if it improves readability or reduces complexity.
- New patterns or exceptions can be added as the project evolves.
In short: this guide exists to make the project easier to understand and maintain, not to create unnecessary friction. Use judgment and prioritize clarity over rigid consistency.
---
Цей посібник з архітектури є **рекомендацією**, а не суворим зводом правил. **Не соромтеся адаптувати та змінювати його відповідно до потреб вашої команди та контексту проєкту**.
Ключові моменти, які варто мати на увазі:
1. **Насамперед — зручність підтримки**.
Головна мета цієї структури — допомогти новому учаснику проєкту швидко зрозуміти, де вносити зміни.
Йдеться про те, щоб знайти правильне місце в коді просто зараз, а не сліпо дотримуватися патерну заради самого патерну.
**Зрештою, головна мета — зробити розробку простішою для всіх.**
2. **Дискусії щодо назв неминучі**.
Будь-який посібник з архітектури може спровокувати суперечки щодо назв файлів, папок або функцій. Використовуйте цей посібник як орієнтир, але пам'ятайте, що практичність і зрозумілість важливіші за суворе дотримання правил.
3. **Гнучкість допускається**.
- Ви можете коригувати шари, структуру модулів або угоди щодо найменування, якщо це покращує читабельність або зменшує складність.
- У міру розвитку проєкту можна додавати нові патерни або винятки.
Коротко: цей посібник існує для того, щоб проєкт був зрозумілішим і простішим у підтримці, а не для того, щоб створювати зайві труднощі. Керуйтеся здоровим глуздом і надавайте перевагу ясності, а не жорсткій послідовності.
## Core Principles
1. Blocks are composable and isolated
- Each block (component, form, widget) has a single responsibility.
- Higher-level blocks (widgets, pages) compose lower-level blocks.
2. Data vs. UI separation
- `data` handles API, state, and business data.
- `view` handles UI and interaction.
- No API logic in UI components — UI calls hooks from `data`.
3. Entities are global types/interfaces
- Placing them in `entities`.
- Any block (data hook, form, widget, page) can use these.
4. Shared
- shared/ = globally reusable components, hooks, utilities.
5. Infrastructure
- infrastructure/ = app-wide technical adapters such as HTTP clients, React Query, storage adapters, and formatting service wrappers.
6. Reference data
- refdata/ = app-wide API-backed lookup datasets shared by multiple modules.
## Recommended Layering
```plaintext
src/
├── data/ # API, react-query hooks, DTOs
├── entities/ # global types and interfaces
├── infrastructure/ # technical adapters: HTTP, query, storage, error handling
├── providers/ # global providers (router, query, session, i18n, theme)
├── refdata/ # API-backed common reference datasets
├── shared/ # reusable global components, hooks, libs, assets, styles
├── view/ # UI layer (blocks)
│ ├── components/ # Feature-level reusable components (ProductCard, LoginButton, VehicleSearchInput)
│ ├── forms/ # smart form components (ProductForm, UserForm)
│ └── widgets/ # composed smart, multi-entity components (DashboardStatsWidget, UserProfileWidget)
└── pages/ # route-level pages
```
## Architecture Scope
The layer structure can be used at three levels:
| Scope | Example | Meaning |
| --------------------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| App-level architecture | `apps/admin/src/app`, `apps/admin/src/shared` | Global app shell, providers, routing, layout, infrastructure, reusable app-wide building blocks. |
| Core module architecture | `apps/admin/src/core/FileManager` | Built-in product modules shipped with the admin template. Each large core module can own its own `data`, `entities`, `providers`, `pages`, `view`, and `shared`. |
| Project module architecture | `apps/admin/src/modules/*` | Generated or project-specific modules. Each module should follow the same layer rules inside its own folder when it becomes more than a simple page. |
For small modules, do not create empty layers only to match the template. Add a layer when the module has real ownership for that layer.
### Admin App Interpretation
`apps/admin` is a reusable admin panel template, not a simple single-purpose React app.
The app shell owns cross-cutting concerns:
- routing and route registration
- global providers
- layouts
- i18n setup
- shared UI
- technical infrastructure
- module registration
Admin app infrastructure that is shared by every module lives in `apps/admin/src/infrastructure`.
Examples include HTTP clients, React Query helpers, storage adapters, and error normalization.
Admin-wide API-backed reference datasets live in `apps/admin/src/refdata`.
Examples include country lists, common option datasets, and other reusable data that comes from the backend but is not owned by one feature module.
Each feature module owns its feature concerns:
- API calls and query hooks in module `data`
- module domain types in module `entities`
- module-local contexts and stores in module `providers`
- route-level screens in module `pages`
- module UI in module `view`
- module-only helpers in module `shared`
If a helper or component becomes useful across multiple modules, move it upward to `apps/admin/src/shared` or a package under `packages/`.
## Layer Dependency Rules
Each layer has defined set of layers it is allowed to import from.
Imports must only flow **downward** — a lower layer never knows about a higher one.
Violating this creates circular dependencies and couples layers that should be independent.
```mermaid
graph TD
pages["pages/"]
view["view/"]
data["data/"]
providers["providers/"]
infrastructure["infrastructure/"]
refdata["refdata/"]
entities["entities/"]
shared["shared/"]
pages --> view
pages --> data
pages --> refdata
pages --> providers
pages --> infrastructure
pages --> entities
pages --> shared
view --> data
view --> refdata
view --> infrastructure
view --> entities
view --> shared
refdata --> infrastructure
refdata --> shared
providers --> data
providers --> refdata
providers --> infrastructure
providers --> shared
data --> infrastructure
data --> entities
data --> shared
infrastructure --> shared
```
| Layer | Can import from |
| ----------------- | --------------------------------------------------------------------------- |
| `shared/` | nothing — zero app-layer dependencies |
| `entities/` | nothing — zero app-layer dependencies |
| `infrastructure/` | `shared/`, external packages, app runtime config |
| `refdata/` | `infrastructure/`, `shared/` |
| `data/` | `infrastructure/`, `entities/`, `shared/` |
| `providers/` | `infrastructure/`, `data/`, `refdata/`, `shared/` |
| `view/` | `data/`, `refdata/`, `entities/`, `shared/`, generic infrastructure helpers |
| `pages/` | all layers |
### Rules
1. **`shared/` has no imports** — it is infrastructure and UI primitives only. It must not reference any domain type or application layer. If a utility needs a domain type, it does not belong in `shared/`.
2. **`entities/` has no imports** — No logic, no React, no external libraries.
3. **`data/` never imports from `view/` or `pages/`** — data hooks and API functions are completely independent of the UI.
4. **`view/` never imports from `pages/`** — components, forms, and widgets do not depend on page-level composition.
5. **The boundary between `shared/ui/` and `view/components/` is enforced by this rule** — if a component needs to import from `entities/`, it cannot live in `shared/`.
6. **`infrastructure/` contains technical adapters, not feature logic** — HTTP clients, React Query, storage adapters, and formatting service wrappers live here.
7. **`refdata/` contains app-wide lookup data** — country lists, languages, currencies, and other shared backend datasets live here instead of inside one feature module.
### 1. **Entities** Layer
The `entities` layer contains global types and interfaces that define your domain models.
- Define entity types for your business objects: User, Product, Message
- Define enums or constants
- No Framework
- No API or hook logic
### 2. **Data** layer (Data Access Layer)
#### Purpose
The `data` layer is responsible for all interactions with external or internal data sources and state management hooks.
It is the only layer that knows how to fetch, mutate, and map data, while higher layers (`forms`, `widgets`, `pages`) just consume the hooks or data from this layer.
#### Responsibilities
- Handle API calls
- Provide React Query hooks or other state management hooks for data fetching/mutations
- Define DTOs (Data Transfer Objects)
- Provide DTO mappers (map raw API data to app entities or view models)
- Define types/interfaces specific to API responses
- Isolate side-effects like HTTP requests, error handling, or caching
#### Rules and Guidelines
1. Only data and state logic belongs here
- No UI, forms, or layout logic
2. Use React Query (or other state library) for fetching/mutations
- Expose hooks, not raw API functions, to the view layer
3. DTOs + Mappers
- Map API response to internal entities/ types
- Keep external API structure isolated from your app
- Mappers should be covered by unit tests because they define the data-layer contract between API DTOs and app entities
4. Types/Interfaces
- Define API-specific types
- Use entities/ for app-wide types
5. No direct imports from view/ or pages/
- data/ is completely independent
### Infrastructure Layer
The `infrastructure` layer contains app-wide technical adapters.
In the admin app, it lives in `apps/admin/src/infrastructure/`.
Examples:
- configured HTTP clients and interceptors
- React Query helpers, response types, and error normalization
- storage or browser adapters
- app-wide formatting service wrappers such as date and name formatters
This layer should stay feature-agnostic. Feature API modules should use infrastructure clients instead of creating their own clients.
### Reference Data Layer
The `refdata` layer contains app-wide reference datasets.
In the admin app, it lives in `apps/admin/src/refdata/`.
Examples:
- countries
- languages
- currencies
- time zones
- common option dictionaries
Use this layer for backend-backed lookup data shared by multiple modules. If the data belongs to one feature module, keep it in that module's `data/` layer.
### 3. **Providers** layer
The `providers` layer is responsible for global context, app-wide state, and services that need to be available throughout the React application.
It sets up everything that any component or page might need: routing, state management, internationalization, theme, notifications, etc.
### 4. **Pages** layer
- `pages` = the highest-level blocks
- Compose `widgets` + `forms` + `components` + `data` into full pages
- Only page-level layout, routing, and orchestration logic lives here
- Example: `UserPage` → uses `UserProfileWidget` + `UserActivityWidget`
### 5. **View** layer
1. **view/components/**
- “Dumb” components
- Pure UI, no business logic
- Example: `ProductCard`, `UserAvatar`
2. **view/forms/**
- Form components
- Encapsulate form state, validation, and submit logic
- Use hooks from `data/` for mutations/queries
- Example: `CreateProductForm` → uses `useCreateProduct()` from `data/hooks`
3. **view/widgets/**
- Composed components
- Combine `components` + `forms` + `shared/ui`
- Feature blocks that can be reused on multiple pages
- Example: `UserProfileWidget` → contains `UserAvatar`, `UserForm`, `UserStatsTable`
### 6. **Shared** layer
The `shared` layer contains global reusable building blocks for the frontend:
- Components, hooks, utilities, assets, styles
- Not tied to a specific feature or entity
- Can be used by `view`, `pages`, `widgets`, or `forms`
This is where common "Lego" blocks live.
#### Responsibilities
- Provide reusable UI components (`Button`, `Input`, `Modal`, `Card`)
- Provide reusable hooks (`useToggle`, `useDebounce`, `useLocalStorage`)
- Provide utility functions (`formatDate`, `errorHandler`, http helpers)
- Provide assets (icons, images, fonts)
- Provide global styles (CSS variables, global CSS, Tailwind overrides)
#### Rules
- Components here are dumb, reusable, feature-agnostic
- Hooks here are generic, not tied to a single entity or feature
- Assets can be imported anywhere
- Styles are global or utility-level, not component-specific
## Full documentation
- [Entities Layer](./layers/entities.md)
- [Data Layer](./layers/data.md)
- [Infrastructure Layer](./layers/infrastructure.md)
- [Reference Data Layer](./layers/refdata.md)
- [Providers Layer](./layers/providers.md)
- [View Layer](./layers/view.md)
- [Shared Layer](./layers/shared.md)
- [File & Folder Naming Convention](./naming.md)
@@ -0,0 +1,260 @@
# Data Layer
## Table of Contents
- [Data Layer](#data-layer)
- [Purpose](#purpose)
- [Responsibilities](#responsibilities)
- [Data Layer Organization Approaches](#data-layer-organization-approaches)
- [By Module](#by-module)
- [By Entity](#by-entity)
- [Data Flow](#data-flow)
- [DTO Types](#1-dto-types)
- [API Layer](#2-api-layer)
- [Mapper](#3-mapper)
- [Mapper Testing](#mapper-testing)
- [Query Hooks](#4-query-hooks)
- [Pagination](#pagination)
## Purpose
The `data` layer is responsible for **all data access and data transformation** in the application.
It is the only layer that knows:
- how to call APIs
- how to map API responses (DTOs) to application entities
- how to handle caching, queries, and mutations
Higher layers (`forms`, `widgets`, `pages`) **do not interact with APIs directly**.
Instead, they consume **hooks provided by the data layer**.
## Responsibilities
The `data/` layer is responsible for:
- performing **API calls**
- defining **DTO types (Data Transfer Objects)**
- converting DTOs → **entities** using **mappers**
- providing **React Query hooks**
- handling **side effects** (requests, caching, errors)
- defining **API-related types**
## Data Layer Organization Approaches
```plaintext
data/
| --- user/
| | --- api.ts # user-related API functions (getUsers, createUser, etc.)
| | --- hooks.ts # React Query hooks (useUsersQuery, useCreateUserMutation)
| | --- dto.ts # API DTO types for user
| | --- mapper.ts # map user DTO → entity
| | --- types.ts # API types/interfaces
|
| --- pagination/
| | --- dto.ts
| | --- mapper.ts
| | --- types.ts
```
There are two main ways to structure the `data` layer:
- By Module — grouped by type of file (API, hooks, DTOs, mappers, types)
- By Entity — grouped by business feature/entity (user, product, message)
| Approach | Best For | Pros | Cons |
| --------- | ------------------------------- | -------------------------------------------- | ---------------------------------------------- |
| By Module | Small apps, simple data layers | Easy to find all API/hooks/DTOs in one place | Files for one feature scattered; hard to scale |
| By Entity | Medium/large apps, scalable CRM | Encapsulated, modular, easier to maintain | Harder to see all APIs/hooks globally |
### By Module
```plaintext
data/
| --- api/ # API request functions
| | --- userAPI.ts
| | --- productAPI.ts
| | --- messageAPI.ts
|
| --- hooks/ # React Query hooks or other data hooks
| | --- useUserQuery.ts
| | --- useCreateProductMutation.ts
| | --- useMessages.ts
|
| --- dto/ # API DTO types
| | --- userDTO.ts
| | --- productDTO.ts
| | --- messageDTO.ts
|
├ --- mappers/ # DTO → entity mappers
| | --- userMapper.ts
| | --- productMapper.ts
|
| --- types/ # API types/interfaces
```
### By Entity
```plaintext
data/
| --- user/
| | --- api.ts # user-related API functions (getUsers, createUser, etc.)
| | --- hooks.ts # React Query hooks (useUsersQuery, useCreateUserMutation)
| | --- dto.ts # API DTO types for user
| | --- mapper.ts # map user DTO → entity
| | --- types.ts # API types/interfaces (optional)
|
| --- product/
| | --- api.ts
| | --- hooks.ts
| | --- dto.ts
| | --- mapper.ts
| | --- types.ts
```
## Data Flow
### 1. DTO Types
```ts
export type UserDTO = {
id: number;
full_name: string;
email_address: string;
};
export type CreateUserDTO = {
full_name: string;
email_address: string;
};
```
### 2. API Layer
API functions or class methods perform HTTP requests. API always returns DTO objects.
```ts
export class UserApi {
private http: HttpClient;
async getDetailed(id: number): Promise<UserDTO> {
return this.http.get(`/users/${id}`);
}
async getList(): Promise<UserDTO[]> {
return this.http.get(`/users`);
}
async create(dto: CreateUserDTO): Promise<UserDTO> {
return this.http.post(`/users`, dto);
}
}
```
### 3. Mapper
Mappers convert `DTO` to `Entity`
```ts
import { User } from "@/entities/user";
export class UserMapper {
toEntity(dto: UserDTO): User {
return {
id: dto.id,
name: dto.full_name,
email: dto.email_address,
};
}
toEntities(dtos: UserDTO[]): User[] {
return dtos.map((dto) => this.toEntity(dto));
}
toPaginated(dto: PaginatedDataDTO<UserDTO[]>): PaginatedResult<User[]> => {
return {
meta: this.paginationMetaMapper.toMeta(dto.meta),
data: this.toEntities(dto.data),
};
};
}
```
### Mapper Testing
Mappers are part of the data layer contract and should have unit tests.
Mapper tests are required when a mapper:
- renames fields from API DTOs to app entities
- normalizes values, paths, dates, sizes, statuses, enums, or defaults
- maps nested DTOs through other mappers
- maps arrays, paginated data, or optional API fields
- protects the app from backend response shape changes
These tests should focus only on transformation behavior. Do not mock React, render components, or call real APIs in mapper tests.
Recommended test coverage:
- `toEntity` maps a representative DTO to the expected entity
- `toEntities` maps arrays consistently
- optional or missing DTO fields use the correct defaults
- nested mappers preserve the expected entity shape
- enum/status casts are covered with expected values
For apps that keep tests outside production source, use a test folder at the app root:
```plaintext
apps/admin/
├── src/
│ └── core/FileManager/data/mappers/entryMapper.ts
└── tests/
└── unit/core/FileManager/data/mappers/entryMapper.test.ts
```
### 4. Query Hooks
Hooks combine API + mapper + React Query.
```ts
export const useUserDetailed = (id: number) => {
const api = new UserApi(http);
const mapper = new UserMapper();
return useQuery({
queryKey: ["user", id],
queryFn: async () => {
const dto = await api.getDetailed(id);
return mapper.toEntity(dto);
},
});
};
```
### Pagination
Pagination is not **entity-specific**, so it should live in a shared data module.
```ts
export type PaginatedMetaDTO = {
total: number;
perPage: number;
currentPage: number;
lastPage: number;
};
export type PaginatedDTO<T> = {
data: T;
meta: PaginatedMetaDTO;
};
export class PaginationMapper {
toMeta(dto: PaginatedMetaDTO) {
return {
total: dto.total,
perPage: dto.perPage,
currentPage: dto.currentPage,
lastPage: dto.lastPage,
};
}
}
```
@@ -0,0 +1,53 @@
# Entities
## Purpose
The `entities` layer defines the **core domain models of the application**.
Entities represent **business objects** used throughout the system, such as `User`, `Product`, `Project`, or `Message`.
This layer contains **pure type definitions** that describe how data is structured inside the application.
Entities are **stable, reusable models** that can be used across any layer.
```plaintext
entities/
| --- user.ts # { id: string; name: string; email: string }
| --- product.ts # { id: string; name: string; price: number }
| --- message.ts # { id: string; content: string; createdAt: string }
| --- enums.ts # { UserRole, ProductCategory }
```
or
```plaintext
entities/
| --- user/
| | --- interfaces.ts # { id: string; name: string; email: string }
| | --- types.ts # { id: string; name: string; email: string }
| | --- enums.ts # { UserRole }
| | --- rules.ts # MIN_ALLOWED_AGE = 18
| | --- index.ts # export all entity types
| --- product.ts # { id: string; name: string; price: number }
| --- message.ts # { id: string; content: string; createdAt: string }
```
## Responsibilities
The entities layer is responsible for:
- defining **domain models**
- defining **entity-related enums or constants**
- providing **shared types used across the application**
```ts
export type User = {
id: number;
name: string;
email: string;
};
export enum UserRole {
ADMIN = "admin",
SIMLPE_JACK = "simple_jack",
}
```
@@ -0,0 +1,106 @@
# Infrastructure Layer
## Table of Contents
- [Purpose](#purpose)
- [Responsibilities](#responsibilities)
- [Recommended Structure](#recommended-structure)
- [Rules](#rules)
- [Examples](#examples)
## Purpose
The `infrastructure/` layer contains app-wide technical adapters and integration helpers.
Use this layer for code that connects the frontend application to technical systems or shared runtime services, not for feature-specific business logic.
## Responsibilities
The `infrastructure/` layer is responsible for:
- HTTP clients and request/response interceptors
- React Query shared helpers, response types, query key helpers, and error normalization
- storage, browser, or runtime adapters
- shared formatting/service adapters such as date or name formatting services
- app-wide technical configuration wrappers
## Recommended Structure
```plaintext
infrastructure/
├── axios/
│ ├── index.ts # configured axios client
│ ├── interceptors.ts # request/response interceptors
│ └── axios.d.ts # axios type augmentation
│
├── reactQuery/
│ ├── index.ts
│ ├── errors.ts # query error handling helpers
│ ├── helpers.ts # query key and pagination helpers
│ ├── queryParams.ts # table/query-param mapping helpers
│ └── types.ts # shared API response/query types
│
├── IntlDate.ts # date formatting service adapter
└── IntlName.ts # name formatting service adapter
```
## Rules
1. Keep infrastructure feature-agnostic.
- No module-specific business rules
- No page or component composition
- No entity mapping that belongs in a module `data/mapper`
2. API modules should use infrastructure clients instead of creating their own clients.
```ts
import { axiosClient } from "#infrastructure/axios";
```
3. React Query helpers that are shared across modules belong here.
Examples: query key builders, list response types, pagination helpers, error extraction helpers.
4. UI layers must not call raw HTTP clients directly.
View and page code should call data-layer hooks. Direct `#infrastructure/axios` usage belongs in API/data modules, not in components.
5. Generic formatting adapters may be consumed by UI when they are not tied to a feature.
Examples: `IntlDate`, `IntlName`.
6. Prefer dependency injection for app-state coupling.
If an interceptor needs auth or query-client behavior, keep that coupling narrow and documented. Avoid importing random feature modules into infrastructure.
## Examples
API modules use the configured HTTP client:
```ts
import { axiosClient } from "#infrastructure/axios";
export class UserAPI {
getDetailed(id: number) {
return axiosClient.get(`/users/${id}`).then((response) => response.data);
}
}
```
Data/query modules use shared query helpers:
```ts
import {
createKeyBuilder,
getPaginationInfo,
} from "#infrastructure/reactQuery";
export const usersCacheKey = createKeyBuilder("USERS", "LIST");
```
UI can consume generic app-wide formatters:
```ts
import { IntlDate } from "#infrastructure/IntlDate";
```
@@ -0,0 +1,96 @@
# Providers Layer
## Table of Contents
- [Providers Layer](#providers-layer)
- [Purpose](#purpose)
- [Responsibilities](#responsibilities)
- [Recommended Structure](#recommended-structure)
- [App Provider Composition](#app-provider-composition)
- [Example: AppProviders](#example-appproviders)
## Purpose
The `providers` layer is responsible for **global application context and environment configuration**.
It acts as the **composition root of the application**, where all global React providers are initialized and combined.
Providers make global services available across the entire application, such as:
- routing
- data fetching clients
- theme configuration
- authentication/session context
- internationalization
- notifications
- global state management
Instead of importing these providers in many places, they are **composed once and applied at the application root**.
## Responsibilities
The `providers` layer is responsible for:
- initializing global React context providers
- configuring global services
- composing multiple providers into a single entry point
- exposing the provider tree to the application root
## Recommended Structure
```plaintext
providers/
|
| --- AppProviders.tsx # main provider composition
|
| --- QueryProvider.tsx # React Query setup
| --- RouterProvider.tsx # routing configuration
| --- ThemeProvider.tsx # theme context
| --- AuthProvider.tsx # authentication/session provider
| --- I18nProvider.tsx # internationalization provider
|
| --- index.ts # re-export providers (optional)
```
## App Provider Composition
Instead of wrapping the application with multiple providers manually, they are composed in a single root provider component.
This centralizes the provider tree and simplifies application initialization.
Example usage in the application entry point:
```tsx
import { AppProviders } from "@/providers/AppProviders";
ReactDOM.createRoot(document.getElementById("root")!).render(
<AppProviders>
<App />
</AppProviders>,
);
```
### Example: AppProviders
This component acts as the global application wrapper.
```tsx
import { ReactNode, FC, PropsWithChildren } from "react";
import { QueryProvider } from "./QueryProvider";
import { ThemeProvider } from "./ThemeProvider";
import { AuthProvider } from "./AuthProvider";
import { RouterProvider } from "./RouterProvider";
import { I18nProvider } from "./I18nProvider";
export const AppProviders: FC<PropsWithChildren> = ({ children }) => {
return (
<QueryProvider>
<I18nProvider>
<AuthProvider>
<ThemeProvider>
<RouterProvider>{children}</RouterProvider>
</ThemeProvider>
</AuthProvider>
</I18nProvider>
</QueryProvider>
);
};
```
@@ -0,0 +1,109 @@
# Reference Data Layer
## Table of Contents
- [Purpose](#purpose)
- [Responsibilities](#responsibilities)
- [Recommended Structure](#recommended-structure)
- [Rules](#rules)
- [Examples](#examples)
## Purpose
The `refdata/` layer contains app-wide reference datasets that are reused across modules.
Reference data is data that usually comes from the backend, but is not owned by one feature module.
Examples:
- countries
- languages
- currencies
- time zones
- common option dictionaries
- shared lookup datasets
## Responsibilities
The `refdata/` layer is responsible for:
- fetching common reference datasets
- exposing reusable query hooks for those datasets
- defining cache keys for reference data
- keeping app-wide lookup data out of feature modules
## Recommended Structure
For small datasets, a single file per dataset is enough:
```plaintext
refdata/
├── countries.ts
├── languages.ts
└── currencies.ts
```
For larger datasets, use a folder:
```plaintext
refdata/
└── countries/
├── api.ts
├── dto.ts
├── mapper.ts
├── queries.ts
└── keys.ts
```
## Rules
1. Use `refdata/` only for app-wide datasets.
If a dataset belongs to one feature module, keep it in that module's `data/` layer.
2. `refdata/` may use infrastructure.
Reference data hooks can use `#infrastructure/axios` and `#infrastructure/reactQuery`.
3. `refdata/` should not import from `view/`, `pages/`, or feature modules.
It should remain reusable across the app.
4. Expose hooks or small service functions.
Consumers should not need to know backend endpoints or cache key details.
5. Add mappers when backend DTOs differ from app-facing reference data.
If a reference dataset performs DTO transformation, test the mapper the same way module data mappers are tested.
## Examples
```ts
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { axiosClient } from "#infrastructure/axios";
import { createKeyBuilder } from "#infrastructure/reactQuery";
export const countriesCacheKey = createKeyBuilder(
"REF_DATA",
"COUNTRIES",
"LIST",
);
interface CountryData {
code: string;
name: string;
}
export const useCountries = () => {
return useQuery({
queryKey: countriesCacheKey.store(),
placeholderData: keepPreviousData,
queryFn: async () => {
return axiosClient
.get<Array<CountryData>>("/datasets/countries")
.then((data) => data.data);
},
});
};
```
@@ -0,0 +1,252 @@
# Shared Layer
## Table of Contents
- [Purpose](#purpose)
- [Recommended Structure](#recommended-structure)
- [UI Components](#ui-components)
- [Purpose](#ui-components-purpose)
- [Core Principles](#ui-components-core-principles)
1. [Simple Components](#ui-components-core-principles-1)
2. [Composable Design](#ui-components-core-principles-2)
3. [Easily Customizable](#ui-components-core-principles-3)
4. [No Business Logic](#ui-components-core-principles-4)
- [Styling Strategy](#ui-components-styling-strategy)
- [Styling Philosophy](#ui-components-styling-strategy-philosophy)
- [Technology Stack](#ui-components-styling-strategy-tech-stack)
- [Hooks](#hooks)
- [Utilities](#utilities)
- [Libraries](#libraries)
- [Assets](#assets)
- [Styles](#styles)
## Purpose
The `shared` layer contains **global reusable building blocks used across the entire application**.
The goal is to keep these utilities **framework-level and reusable across the entire application**.
```plaintext
shared/
├── ui/ # dumb or basic UI components
│ ├── Button.tsx
│ ├── Modal.tsx
│ └── Card.tsx
│
├── widgets/ # reusable composed UI blocks
│ ├── DataGrid/
│ └── screens/
│
├── forms/ # reusable form composition helpers
│ └── composed/
│
├── editors/ # reusable editor integrations
│ ├── tinymce/
│ └── image_input/
│
├── hooks/ # reusable hooks
│ ├── useDebounce.ts
│ ├── useToggle.ts
│ └── useLocalStorage.ts
│
├── lib/ # pure helpers & utilities
│ └── formatDate.ts
│
├── assets/ # images, icons, fonts
│ ├── icons/
│ └── fonts/
│
└── styles/ # global CSS, variables, Tailwind overrides
├── globals.css
└── variables.css
```
## Recommended Structure
```plaintext
shared/
|
| --- ui/ # primitive UI components
|
| --- widgets/ # reusable composed UI blocks
|
| --- forms/ # reusable form composition
|
| --- editors/ # reusable editor wrappers
|
| --- hooks/ # generic reusable hooks
|
| --- utils/ # pure helper functions
|
| --- lib/ # pure helpers and utility services
|
| --- assets/ # images, icons, fonts
|
| --- styles/ # global styling
|
| --- constants/ # global constants
```
### UI Components
#### Purpose {#ui-components-purpose}
The goal of the UI layer is to provide simple, composable, and customizable components that can be reused across the application.
- components are simple building blocks
- components are composable
- components are fully customizable
- components are not tied to business logic
UI components should behave like Lego blocks that can be combined into larger UI structures.
#### Core Principles {#ui-components-core-principles}
##### 1. Simple Components {#ui-components-core-principles-1}
Examples:
- Button
- Input
- Card
- Modal
- Dropdown
##### 2. Composable Design {#ui-components-core-principles-2}
Components must support composition.
Composition allows developers to build complex UI without modifying base components.
Example:
```tsx
<Card>
<CardHeader />
<CardContent />
<CardFooter />
</Card>
```
```tsx
<Button>
<Icon />
Save
</Button>
```
##### 3. Easily Customizable {#ui-components-core-principles-3}
UI components should allow customization through:
- className
- variants
- props
- composition
```tsx
<Button variant="outline" size="lg">
Save
</Button>
```
##### 4. No Business Logic {#ui-components-core-principles-4}
UI components must remain presentation-only. May include local state.
They should NOT:
- call API
- use React Query
- contain form submission logic
- contain domain-specific logic
#### Styling Strategy {#ui-components-styling-strategy}
##### Styling Philosophy {#ui-components-styling-strategy-philosophy}
Styling follows these principles:
- Utility-first styling
- Design tokens for consistency
- Component-level styling
- Variants instead of style duplication
- Minimal custom CSS
##### Technology Stack {#ui-components-styling-strategy-tech-stack}
- **TailwindCSS** - primary styling system
- **class-variance-authority (CVA)** - component variants
- **CSS variables** - design tokens
- **Radix primitives** - accessible UI building blocks
Custom CSS should be minimal and centralized.
### Hooks
The `shared/hooks/` folder contains generic reusable hooks.
These hooks must not depend on entities or business logic.
```plaintext
shared/hooks/
| --- useDebounce.ts
| --- useToggle.ts
| --- usePrevious.ts
| --- useLocalStorage.ts
```
### Utilities
The `shared/utils/` folder contains pure helper functions.
These functions should be stateless and reusable
```plaintext
shared/utils/
| --- formatDate.ts
| --- formatCurrency.ts
| --- generateId.ts
| --- sleep.ts
```
### Libraries
The `shared/lib/` folder contains pure helpers and utility services.
Examples include:
- date formatting helpers
- name formatting helpers
- URL/file path helpers
- phone-number formatting helpers
```plaintext
shared/lib/
| --- time/
| --- phone_number/
| --- resolveFileURL.ts
```
### Assets
The `shared/assets/` folder stores static resources used across the application.
```plaintext
shared/assets/
| --- icons/
| --- fonts/
```
### Styles
The `shared/styles/` folder contains global styling resources.
Typical usage:
- CSS variables
- Tailwind configuration extensions
- global resets
```plaintext
shared/styles/
| --- globals.css
| --- variables.css
| --- tailwind.css
```
@@ -0,0 +1,345 @@
# Views Layer
## Table of Contents
- [Views Layer](#views-layer)
- [Components](#components)
- [Components Purpose](#components-purpose)
- [Components Responsibilities](#components-responsibilities)
- [What Components should NOT do](#what-components-should-not-do)
- [Widgets](#widgets)
- [Widget Purpose](#widget-purpose)
- [Widget Characteristics](#widget-characteristics)
- [Widget Responsibilities](#widget-responsibilities)
- [Forms](#forms)
- [Form Architecture](#form-architecture)
- [Form Logic](#form-logic)
- [Form Validation](#form-validation)
- [Form Values](#form-values)
- [Form Types](#form-types)
- [Submit Functions](#submit-functions)
- [Purpose](#submit-functions-purpose)
- [API Example](#api-example)
- [Responsibilities](#responsibilities)
- [Composed Form Elements](#composed-form-elements)
- [Example: InputSwitch](#example-inputswitch)
- [Usage in a Form](#usage-in-a-form)
## Components
### Components Purpose
Components are reusable UI blocks used to build user interfaces.
They encapsulate presentation and simple UI behavior, but do not contain business logic or data fetching. `UserAvatar`, `ProductBadge`
Components are designed to be small, composable, and reusable across multiple parts of the application.
### Components Responsibilities:
- render UI
- accept props
- handle simple UI state (hover, toggle, open/close)
- compose other components
### What **Components** should NOT do
Components should not contain logic such as:
- API calls
- mutations
- complex state management
- business workflows
## Widgets
### Widget Purpose
Widgets are **self-contained UI blocks that implement a specific piece of functionality**.
Unlike simple components, widgets combine **UI, logic, and interactions** to deliver a complete user-facing feature.
Widgets are typically used to **compose pages** and represent meaningful parts of the interface such as panels, sections, or complex interactive blocks. Widgets should remain modular building blocks.
### Widget Characteristics
A widget usually:
- encapsulates **UI and feature logic**
- composes **multiple components**
- may include **forms, dialogs, or actions**
- can interact with **data**
- exposes a **simple API for pages**
Widgets should hide their internal implementation and provide a **clean interface** to the outside.
### Widget Responsibilities
Widgets may:
- orchestrate UI interactions
- manage local UI state
- connect to features or data hooks
- combine multiple components/forms into a functional block
- may fetch data or rely on data passed in props
- A **widget** can use another **widget** if:
- the nested widget represents a clear functional sub-block
- the parent widget is grouping or orchestrating UI
- the dependency does not create tight coupling
```ts
export const ProjectOverviewWidget = ({ projectId }: Props) => {
return (
<div>
<ProjectStatsWidget projectId={projectId} />
<ProjectActivityWidget projectId={projectId} />
<ProjectMembersWidget projectId={projectId} />
</div>
);
};
```
## Forms
### Form Architecture
```plaintext
view/forms/SomeForm/
| --- form.ts
| --- Component.tsx
| --- submit.ts
| --- constants.ts # Optional
| --- schema.ts # Optional validation schema. Good for large forms or multistep forms
| --- index.ts
```
| File | Responsibility |
| --------------- | ---------------------------------- |
| `form.ts` | validation schema, types, defaults |
| `Component.tsx` | UI + react-hook-form integration |
| `submit.ts` | Submission logic |
| `constants.ts` | form IDs, UI constants |
| `index.ts` | public exports |
### Form Logic
`form.ts` contains pure form logic.
It should include:
- validation schema
- form types
- default values
- helper templates
#### Form Validation
```ts
export const validationSchema = z.object({
first_name: z.string().min(3),
last_name: z.string().min(3),
});
```
If you use `i18n` you can create function that returns validation schema.
```ts
function createValidationSchema(
translationFn: TFunction<"translation", undefined>, // Depend on libraty you use
) {
return z.object({
first_name: z.string({ message: translationFn("required") }),
last_name: z.string({ message: translationFn("required") }),
});
}
```
> [! WARNING]
> Dont include `i18n` singleton last_name: z.string().min(1, i18n.t('required'))
> It will not translate errors when language state change
#### Form Values
Default values should be defined using factory functions.
Avoid inline default objects.
```ts
export function createFormValues(): FormValues {
return {
first_name: "",
last_name: "",
};
}
```
```ts
export function toFormValues(data: SomeData): FormValues {
return {
first_name: data?.first_name ?? "",
last_name: data?.last_name ?? "",
};
}
```
#### Form Types
Define types for form values and submit functiuons
```ts
export type FormValues = z.infer<typeof validationSchema>;
export type SubmitFunc = (
values: FormValues,
handlers: UseFormReturn<FormValues>,
) => void;
```
#### Submit Functions
##### Purpose {#submit-functions-purpose}
The `useSubmit` hook standardizes form submission logic:
- Handles create and update operations
- Integrates with `data/` layer hooks (`useCreate`, `useUpdate`)
- Automatically handles validation errors from the API
- Invalidates lists or detailed queries to refresh UI
- Exposes a unified API for forms
This pattern allows keep submission logic centralized.
##### API Example
```ts
interface Params {
onSuccess?: (id?: number) => void;
}
export const useSubmit = (params?: Params) => {
const onSuccess = params?.onSuccess;
const { mutate: createMutation, isPending: isCreatePending } = useCreate();
const { mutate: updateMutation, isPending: isUpdateending } = useUpdate();
const handleSubmit: SubmitFunc = (values, handlers) => {
if (values.id) {
handleUpdate(values, handlers);
} else {
handleCreate(values, handlers);
}
};
const handleCreate: SubmitFunc = (values, handlers) => {
createMutation(values, {
onSuccess() {
invalidateList();
if (typeof onSuccess === "function") onSuccess();
},
onError(error) {
extractValidationErrors(error).forEach((e) => {
handlers.setError(e.field as keyof typeof values, {
type: "apiError",
message: e.message,
});
});
},
});
};
const handleUpdate: SubmitFunc = (values, handlers) => {
if (!values.id) return;
updateMutation(
{ ...values, id: values.id },
{
onSuccess() {
invalidateList();
if (values.id) invalidateDetailed(values.id);
if (typeof onSuccess === "function") {
onSuccess(values.id ?? undefined);
}
},
onError(error) {
extractValidationErrors(error).forEach((e) => {
handlers.setError(e.field as keyof typeof values, {
type: "apiError",
message: e.message,
});
});
},
},
);
};
return {
submit: handleSubmit,
isSubmitting: isCreatePending || isUpdateending,
};
};
```
##### Responsibilities
1. Determine operation type
- If values.id exists -> update
- Otherwise -> create
2. Call the appropriate mutation
- `createMutation` or `updateMutation` from `data` layer
- Hooks handle API calls internally
3. Handle success
- Invalidate queries (list/detail)
- Trigger optional `onSuccess` callback
4. Handle errors
- Extract validation errors from API
- Set errors on form fields using `handlers.setError`
5. Expose state
- `submit` -> function to call from form
- `isSubmitting` -> combines pending state from both mutations
#### Composed Form Elements
To avoid repetitive boilerplate code when building forms, we encourages creating composed form elements that integrate React Hook Form with shared UI components.
- Combines label, input, validation message, and layout in one reusable component.
- Can encapsulate any input type: text, switch, select, date, textarea, etc.
- Works seamlessly with FormProvider for context propagation.
##### Example: InputSwitch
InputSwitch wraps a Switch component into a reusable form field:
```tsx
<InputSwitch
name="notificationsEnabled"
label="Enable notifications"
description="Toggle notifications on/off"
required
/>
```
Key points:
- Uses `useFormContext` to access form control.
- Wraps `FormField`, `FormItem`, `FormLabel`, `FormMessage`, and `FormControl`.
- Handles required and disabled states.
- This allows you to reuse the same input type across forms without writing repetitive boilerplate.
##### Usage in a Form
```tsx
<FormProvider {...form}>
<Component.FormElement
id={MAIN_FORM_ID}
onSubmit={form.handleSubmit((values) => onSubmit(values, form))}
>
<Component.FormGrid columns={2}>
<Component.GridElement colSpan={1}>
<Component.InputSwitch
name="notificationsEnabled"
required
label="Enable notifications"
/>
</Component.GridElement>
</Component.FormGrid>
</Component.FormElement>
</FormProvider>
```
@@ -0,0 +1,249 @@
# File & Folder Naming
## Table of Contents
- [Naming Convention](#naming-convention)
- [Folders](#folders)
- [Component Files](#component-files)
- [Hook Files](#hook-files)
- [Utility & Helper Files](#utility--helper-files)
- [Other TypeScript Files](#other-typescript-files)
- [Examples by Layer](#examples-by-layer)
- [Case-Only Rename Problem (macOS + Git)](#case-only-rename-problem-macos--git)
- [Required: Correct Rename Procedure](#required-correct-rename-procedure)
- [Making Git Case-Sensitive on macOS](#making-git-case-sensitive-on-macos)
- [Important Reality Check](#important-reality-check)
## Naming Convention
We follow **standard React community naming conventions**:
These conventions apply to frontend apps and to module folders inside those apps. For example, `apps/admin/src/core/FileManager` may use PascalCase because it is a product module folder, while its layer folders such as `data`, `entities`, `providers`, `pages`, `view`, and `shared` stay lowercase.
| Type | Convention | Example |
| ---------------------- | -------------------------------------------- | ------------------------------------- |
| Folders | lowercase (PascalCase for component folders) | `user/`, `shared/`, `CreateUserForm/` |
| React component files | PascalCase | `UserProfile.tsx`, `Button.tsx` |
| Hook files | camelCase (`use` prefix) | `useDebounce.ts`, `useToggle.ts` |
| Utility / helper files | camelCase | `formatDate.ts`, `generateId.ts` |
| API / service files | camelCase (acronyms uppercase) | `userAPI.ts`, `productAPI.ts` |
| DTO / mapper files | camelCase (acronyms uppercase) | `userDTO.ts`, `userMapper.ts` |
| Type / interface files | camelCase | `types.ts`, `dto.ts`, `interfaces.ts` |
| Constants files | camelCase | `constants.ts`, `formConstants.ts` |
| Config / entry files | camelCase | `index.ts`, `queryClient.ts` |
### Folders
Layer and infrastructure folders use **lowercase**.
Component, form, and widget folders use **PascalCase** — matching the component they contain.
```
src/
data/ # lowercase — layer folder
user/ # lowercase — entity folder
product/
view/ # lowercase — layer folder
components/
forms/
CreateUserForm/ # PascalCase — component folder
widgets/
UserProfileWidget/
shared/
ui/
hooks/
lib/
```
### Component Files
React component files use **PascalCase** — matching the name of the exported component.
```
UserProfile.tsx
ProductCard.tsx
LoginButton.tsx
UserProfileWidget.tsx
CreateProductForm.tsx
```
### Hook Files
Hook files use **camelCase** and always start with `use`.
```
useDebounce.ts
useToggle.ts
useLocalStorage.ts
useUserQuery.ts
useCreateProductMutation.ts
```
### Utility & Helper Files
Utility and helper files use **camelCase**.
```
formatDate.ts
formatCurrency.ts
generateId.ts
sleep.ts
```
### Other TypeScript Files
All other `.ts` files (APIs, DTOs, mappers, types, config) use **camelCase**, with acronyms kept uppercase.
```
userAPI.ts
userDTO.ts
userMapper.ts
types.ts
constants.ts
queryClient.ts
axiosInstance.ts
```
### Examples by Layer
#### `shared/`
```plaintext
shared/
├── ui/
│ ├── Button.tsx
│ ├── Modal.tsx
│ └── Card.tsx
│
├── hooks/
│ ├── useDebounce.ts
│ ├── useToggle.ts
│ └── useLocalStorage.ts
│
├── lib/
│ ├── axios/
│ │ └── axiosInstance.ts
│ └── query/
│ └── queryClient.ts
│
└── utils/
├── formatDate.ts
└── formatCurrency.ts
```
#### `data/`
```plaintext
data/
├── user/
│ ├── userAPI.ts
│ ├── hooks.ts # or useUserQuery.ts, useCreateUserMutation.ts
│ ├── userDTO.ts
│ └── userMapper.ts
│
└── product/
├── productAPI.ts
├── hooks.ts
├── productDTO.ts
└── productMapper.ts
```
#### `providers/`
```plaintext
providers/
├── AppProviders.tsx
├── QueryProvider.tsx
├── RouterProvider.tsx
├── ThemeProvider.tsx
├── AuthProvider.tsx
├── I18nProvider.tsx
└── index.ts
```
#### `view/`
```plaintext
view/
├── components/
│ ├── UserAvatar.tsx
│ └── ProductCard.tsx
│
├── widgets/
│ └── UserProfileWidget/
│ ├── index.ts
│ └── UserProfileWidget.tsx
│
└── forms/
└── CreateUserForm/
├── form.ts
├── Component.tsx
├── submit.ts
└── index.ts
```
## Case-Only Rename Problem (macOS + Git)
macOS file systems are **case-insensitive by default**, while Git is **case-sensitive internally**.
Renaming a file like `api.ts` → `Api.ts` may:
- Not be detected by Git
- Break CI (Linux environments are case-sensitive)
- Cause inconsistent repository state
### REQUIRED: Correct Rename Procedure
If you **must rename a file or folder by case only**, you are required to perform a two-step rename.
#### Step 1 — Rename to Temporary Name
```bash
git mv userApi.ts userApi-temp.ts
git commit -m "chore: temporary rename userApi.ts to userApi-temp.ts"
```
#### Step 2 — Rename to Final Name
```bash
git mv userApi-temp.ts UserApi.ts
git commit -m "chore: rename userApi-temp.ts to UserApi.ts (case change)"
```
### Rules
- Never rename by case only in one step
- Always use an intermediate name
- Always commit both steps
- Applies to files and folders
- Do not rely only on IDE rename
- Always verify with `git status`
### Making Git Case-Sensitive on macOS
By default, Git on macOS usually has:
`core.ignorecase=true`
This makes Git ignore case-only changes.
#### Set Case Sensitivity for This Repository
```bash
git config core.ignorecase false
```
#### Verify:
```bash
git config core.ignorecase
```
It should return: `false`
### Important Reality Check
Even if `core.ignorecase=false`:
- macOS file system is still case-insensitive
- Case-only renames can still behave unexpectedly
- The two-step rename process is still the safest approach
+79
View File
@@ -0,0 +1,79 @@
# Admin App Documentation
The admin app is a reusable React admin panel template. It includes the app shell, layouts, shared infrastructure, built-in core modules, development tools, and generated project modules.
Source root:
```txt
apps/admin/
```
Frontend architecture guide:
```txt
docs/apps-architecture/react-frontend/Readme.md
```
## Current Purpose
This page is a documentation stub. The admin app will be documented in more detail while its modules are migrated or aligned to the shared React frontend architecture.
For now, use this page to define ownership and the target documentation structure.
## Architecture Direction
`apps/admin` follows the general React architecture described in [React Frontend Architecture](../../apps-architecture/react-frontend/Readme.md).
Because the admin app is a template and module host, the architecture applies at multiple levels:
1. App shell architecture under `apps/admin/src/app` and `apps/admin/src/shared`.
2. Core module architecture under `apps/admin/src/core/*`.
3. Generated or project module architecture under `apps/admin/src/modules/*`.
Large modules should be structured like small apps inside their own folder:
```txt
ModuleName/
config/
data/
entities/
providers/
pages/
shared/
view/
index.ts
```
Small modules may start with fewer folders. Add layers when the module actually owns API calls, entities, providers, pages, UI blocks, or module-only helpers.
## Source Areas
| Area | Path | Purpose |
| ----------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| App shell | `apps/admin/src/app/` | App routing, providers, layouts, i18n, styles, and global configuration. |
| Infrastructure | `apps/admin/src/infrastructure/` | HTTP, React Query helpers, storage adapters, and technical error handling shared by modules. |
| Reference data | `apps/admin/src/refdata/` | Common API-backed datasets and option lists that are not owned by a single feature module. |
| Core modules | `apps/admin/src/core/` | Built-in admin template modules such as File Manager, auth, account, permissions, dashboard, and admin management. |
| Generated modules | `apps/admin/src/modules/` | Project-specific or generated business modules. |
| Devtools | `apps/admin/src/devtools/` | Developer tools documented in the [Devtools guide](../../devtools/Readme.md). |
| Shared frontend | `apps/admin/src/shared/` | Admin-wide reusable UI, widgets, forms, editors, hooks, and pure utilities. |
## Module Documentation Plan
Each core module or generated module should eventually have documentation that covers:
1. Module purpose.
2. Source path.
3. Routes and pages.
4. Data layer API and query ownership.
5. Entities and DTO mapping.
6. Providers, stores, and local state ownership.
7. View widgets, forms, and reusable components.
8. Backend integration, if the module has backend routes.
9. Known migration gaps from the target architecture.
## Current Module Docs
| Module | Source | Docs |
| ------------ | ---------------------------------- | -------------------------------------------------------------------- |
| File Manager | `apps/admin/src/core/FileManager/` | [File Manager frontend](../../core-modules/file-manager/frontend.md) |
@@ -0,0 +1,53 @@
# Access Control Module
The Access Control module manages admin accounts, roles, and permissions in the admin app. It is the UI boundary for deciding who can access which admin features.
## Source Map
| Area | Path |
| ------------------------- | ------------------------------------------------------- |
| Frontend module | `apps/admin/src/core/AccessControl/` |
| Frontend routes | `apps/admin/src/core/AccessControl/config/routing.tsx` |
| Frontend translations | `apps/admin/src/core/AccessControl/config/translation/` |
| Backend admin module | `backend/app/base_modules/Admin/` |
| Backend role module | `backend/app/base_modules/Role/` |
| Backend permission module | `backend/app/base_modules/Permission/` |
| Permission loader | `backend/app/services/permission_service.ts` |
## Documentation
| Page | Purpose |
| ------------------------- | ------------------------------------------------------------------------------------- |
| [Frontend](./frontend.md) | Routes, UI structure, data layer, forms, translations, and cache invalidation. |
| [Backend](./backend.md) | Admin, role, and permission API routes, policies, validators, and permission loading. |
## Main Capabilities
- List, search, sort, paginate, create, edit, and delete admin accounts.
- Assign roles to admin accounts.
- List, create, edit, and delete roles.
- Assign permissions to roles.
- Load available permission definitions from backend module permission configs.
- Gate access to screens and actions through the Permission core module.
## Ownership Rules
- AccessControl owns management UI for admins, roles, and role permissions.
- Backend Admin owns account persistence and profile update endpoint.
- Backend Role owns role persistence and role-permission sync.
- Backend Permission owns the available permission list endpoint.
- Permission core module owns permission checking and React gates.
- Auth core module owns the current authenticated admin and session state.
## Common Change Guide
| Change | Start Here |
| --------------------------- | ---------------------------------------------------------------------------------------------------- |
| Add an admin field | `entities/AdminEntity.ts`, `data/api/dto.ts`, `data/mappers/AdminMapper.ts`, `view/forms/AdminForm/` |
| Change admin list columns | `view/widgets/AdminTable.tsx` |
| Change role form behavior | `view/forms/RoleForm/` |
| Add a new permission source | Backend module `config/permissions.ts` and `backend/app/services/permission_service.ts` |
| Change cache invalidation | `data/queries/cache.ts` |
| Change translations | `config/translation/en.json` and `config/translation/uk.json` |
| Change backend admin API | `backend/app/base_modules/Admin/controller/admin_controller.ts` |
| Change backend role API | `backend/app/base_modules/Role/controller/role_controller.ts` |
@@ -0,0 +1,89 @@
# Access Control Backend
Access Control frontend uses three backend base modules:
```txt
backend/app/base_modules/Admin/
backend/app/base_modules/Role/
backend/app/base_modules/Permission/
```
## Routes
| Method | Route | Backend | Purpose |
| -------- | ----------------------- | ---------- | ------------------------------------------- |
| `GET` | `/admin` | Admin | Paginated admin list. |
| `GET` | `/admin/:id` | Admin | Admin details with roles. |
| `POST` | `/admin` | Admin | Create admin and sync roles. |
| `PUT` | `/admin/:id` | Admin | Update admin and sync roles. |
| `DELETE` | `/admin/:id` | Admin | Delete admin. |
| `PUT` | `/admin/update-profile` | Admin | Update current authenticated admin profile. |
| `GET` | `/role` | Role | Paginated role list. |
| `GET` | `/role/:id` | Role | Role details with permissions. |
| `POST` | `/role` | Role | Create role and role-permission rows. |
| `PUT` | `/role/:id` | Role | Update role and role-permission rows. |
| `DELETE` | `/role/:id` | Role | Delete role. |
| `GET` | `/permission` | Permission | List all available permission definitions. |
All routes use `middleware.auth()` except login routes owned by Auth.
## Authorization
Policies:
```txt
backend/app/base_modules/Admin/policy/admin_policy.ts
backend/app/base_modules/Role/policy/role_policy.ts
backend/app/base_modules/Permission/policy/permission_policy.ts
```
Permission slugs are defined in:
```txt
backend/app/base_modules/Admin/config/permissions.ts
backend/app/base_modules/Role/config/permissions.ts
```
Current AccessControl slugs:
```txt
admins.view
admins.create
admins.update
admins.delete
role.view
role.create
role.update
role.delete
```
## Permission Loading
Available permissions are collected by:
```txt
backend/app/services/permission_service.ts
```
`getAllPermissions()` scans `app/base_modules/*/config/permissions.ts` and `app/modules/*/config/permissions.ts`. Add permissions to the owning backend module config; do not hard-code them in the frontend.
## Validation
Validators:
```txt
backend/app/base_modules/Admin/validators/admin_create_validator.ts
backend/app/base_modules/Admin/validators/admin_update_validator.ts
backend/app/base_modules/Admin/validators/admin_update_profile_validator.ts
backend/app/base_modules/Role/validator/role_create_validator.ts
backend/app/base_modules/Role/validator/role_update_validator.ts
```
Keep frontend form rules compatible with backend validators. Backend validation is authoritative.
## Data Notes
- Admin create/update syncs assigned roles through the admin-role relation.
- Role create/update rewrites `role_permissions` rows for the role.
- Permission records returned by `/permission` are loaded from permission config files, not only database rows.
- Deleting the final admin is blocked by the Admin controller.
@@ -0,0 +1,145 @@
# Access Control Frontend
The frontend module provides the admin-facing UI for managing admins, roles, and permissions.
Source root:
```txt
apps/admin/src/core/AccessControl/
```
## Directory Structure
```txt
AccessControl/
config/ Routes, menu, messages, translations.
data/
api/ Raw HTTP calls and DTOs.
mappers/ DTO-to-entity mapping.
queries/ React Query hooks, keys, and cache invalidation.
entities/ Admin, role, permission entities and enums.
pages/ Route-level pages.
view/
forms/ Admin and role forms.
layouts/ Module layout.
widgets/ Tables and modal triggers.
```
## Routes
Frontend route config:
```txt
config/routing.tsx
```
Mounted module path:
```txt
/access-control
```
| Route | Page | Purpose |
| ----------------------- | --------------- | ----------------------------------------- |
| `/access-control/admin` | `AdminListPage` | Manage admin accounts and assigned roles. |
| `/access-control/role` | `RoleListPage` | Manage roles and assigned permissions. |
## Data Layer
Raw API calls live in:
```txt
data/api/AdminAPI.ts
data/api/RoleAPI.ts
data/api/PermissionAPI.ts
data/api/dto.ts
```
Query hooks live in:
```txt
data/queries/admin.ts
data/queries/role.ts
data/queries/permission.ts
```
Cache invalidation helpers live in:
```txt
data/queries/cache.ts
```
Current helpers:
```ts
invalidateAdminList();
invalidateAdminDetailed(id?);
invalidateRoleList();
invalidateRoleDetailed(id?);
invalidatePermissionList();
```
## DTO Mapping
The API layer returns DTOs. Query hooks map DTOs into entities before data reaches UI code.
```txt
data/mappers/AdminMapper.ts
data/mappers/RoleMapper.ts
data/mappers/PermissionMapper.ts
entities/AdminEntity.ts
entities/RoleEntity.ts
entities/PermissionEntity.ts
```
Keep backend response shape in DTOs. Keep UI/domain shape in entities.
## Forms
Admin form:
```txt
view/forms/AdminForm/
```
Responsibilities:
- Create and update admin data.
- Select admin status.
- Assign roles.
- Optionally update password.
Role form:
```txt
view/forms/RoleForm/
```
Responsibilities:
- Create and update role name.
- Group available permissions by module slug prefix.
- Assign permission slugs to the role.
## Widgets
| Widget | Purpose |
| -------------------- | -------------------------------------------- |
| `AdminTable` | Admin list, filters, actions, create button. |
| `RoleTable` | Role list, actions, create button. |
| `ManageAdminTrigger` | Admin create/edit dialog orchestration. |
| `ManageRoleTrigger` | Role create/edit dialog orchestration. |
| `DeleteAdminTrigger` | Admin delete confirmation and mutation. |
| `DeleteRoleTrigger` | Role delete confirmation and mutation. |
## Translations
Translations are module-local:
```txt
config/messages.ts
config/translation/en.json
config/translation/uk.json
```
Use only `ACCESS_CONTROL.*` keys inside this module.
@@ -0,0 +1,36 @@
# Account Settings Module
The Account Settings module provides the current admin user's profile settings screen. It lets the signed-in admin update profile fields and password, and view assigned roles.
## Source Map
| Area | Path |
| ------------------------- | ----------------------------------------------------------------------------- |
| Frontend module | `apps/admin/src/core/AccountSettings/` |
| Frontend routes | `apps/admin/src/core/AccountSettings/config/routing.tsx` |
| Frontend translations | `apps/admin/src/core/AccountSettings/config/translation/` |
| Auth provider dependency | `apps/admin/src/core/Auth/providers/AuthProvider/` |
| Backend profile endpoint | `backend/app/base_modules/Admin/controller/admin_controller.ts` |
| Backend profile validator | `backend/app/base_modules/Admin/validators/admin_update_profile_validator.ts` |
## Documentation
| Page | Purpose |
| ------------------------- | --------------------------------------------------------- |
| [Frontend](./frontend.md) | Route, page, form, profile update flow, and translations. |
| [Backend](./backend.md) | Current-user profile endpoint and validation. |
## Main Capabilities
- Display current authenticated admin profile.
- Update first name, last name, email, status, and optional password.
- Show assigned roles.
- Crop and stage an avatar image in the form UI.
- Use module-local translations through `ACCOUNT.*` keys.
## Ownership Rules
- AccountSettings owns the current-user profile UI.
- Auth owns current-user state, token storage, and API calls used by the profile page.
- Backend Admin owns profile persistence and validation.
- AccountSettings should not manage other admins; that belongs to AccessControl.
@@ -0,0 +1,69 @@
# Account Settings Backend
Account Settings does not have a separate backend base module. It uses the Admin backend module's current-user profile endpoint.
## Route
| Method | Route | Controller | Purpose |
| ------ | ----------------------- | ------------------------------- | ----------------------------------------- |
| `PUT` | `/admin/update-profile` | `AdminController.updateProfile` | Update the currently authenticated admin. |
Source:
```txt
backend/app/base_modules/Admin/routes.ts
backend/app/base_modules/Admin/controller/admin_controller.ts
```
The route uses `middleware.auth()`.
## Request Flow
```txt
PUT /admin/update-profile
-> auth middleware
-> AdminController.updateProfile
-> admin_update_profile_validator
-> current auth user merge/save
-> reload roles
-> query role_permissions
-> return { user, permissions }
```
## Validation
Validator:
```txt
backend/app/base_modules/Admin/validators/admin_update_profile_validator.ts
```
Keep frontend validation compatible with this validator. Backend validation is authoritative.
## Response Contract
The endpoint returns the same shape consumed by Auth:
```ts
type GetUserResponse = {
user: {
id: number;
email: string;
firstName: string;
lastName: string;
status: string;
createdAt: string;
roles: Array<{ id: number; name: string }>;
};
permissions: string[];
};
```
AuthProvider maps this response into `AuthUser` and updates current user state.
## Ownership Rules
- AccountSettings owns the UI and form behavior.
- Auth owns `updateUser` and current user state updates.
- Backend Admin owns profile persistence and response shape.
- AccessControl owns managing other admin accounts.
@@ -0,0 +1,103 @@
# Account Settings Frontend
The frontend module implements the current admin profile settings page.
Source root:
```txt
apps/admin/src/core/AccountSettings/
```
## Directory Structure
```txt
AccountSettings/
config/ Routes, messages, translations.
pages/ Profile route page.
view/
forms/ Profile form and form fields.
layouts/ Account layout.
```
## Routes
Frontend route config:
```txt
config/routing.tsx
```
Mounted module path:
```txt
/account/*
```
| Route | Page | Purpose |
| ------------------ | ------------- | ------------------------------- |
| `/account/profile` | `ProfilePage` | Current admin profile settings. |
The index route redirects to `/account/profile`.
## Profile Page
Main file:
```txt
pages/Profile/index.tsx
```
Responsibilities:
- Read the current user from `useAuth()`.
- Submit profile updates through `updateUser`.
- Map backend validation errors into form fields.
- Render assigned roles.
## Form
Profile form files:
```txt
view/forms/ProfileForm/Component.tsx
view/forms/ProfileForm/form.ts
view/forms/ProfileForm/fields/PasswordField.tsx
view/forms/ProfileForm/fields/AvatarCropperField.tsx
```
Password behavior:
- Password fields are hidden until toggled open.
- Password is optional.
- If provided, password must be at least 8 characters.
- If provided, confirmation must match.
Avatar behavior:
- The field supports local crop/preview UI.
- The form currently stages avatar data in the form value.
- Profile update payload strips avatar before calling `updateUser`.
## Auth Dependency
## Translations
Translations are module-local:
```txt
config/messages.ts
config/translation/en.json
config/translation/uk.json
```
Use only `ACCOUNT.*` keys inside this module.
## Common Change Guide
| Change | Start Here |
| ---------------------------- | ----------------------------------------------------------------------------- |
| Add a profile field | `ProfileForm/form.ts`, `ProfileForm/Component.tsx`, backend profile validator |
| Change password validation | `ProfileForm/form.ts` |
| Change avatar UI | `ProfileForm/fields/AvatarCropperField.tsx` |
| Change profile save behavior | `pages/Profile/index.tsx` and AuthProvider `updateUser` |
| Change translations | `config/translation/en.json` and `config/translation/uk.json` |
+39
View File
@@ -0,0 +1,39 @@
# Auth Module
The Auth module owns admin authentication, current-user state, token persistence, auth routes, and route guarding.
## Source Map
| Area | Path |
| --------------------- | -------------------------------------------------- |
| Frontend module | `apps/admin/src/core/Auth/` |
| Frontend routes | `apps/admin/src/core/Auth/config/routing.tsx` |
| Frontend provider | `apps/admin/src/core/Auth/providers/AuthProvider/` |
| Frontend translations | `apps/admin/src/core/Auth/config/translation/` |
| Backend module | `backend/app/base_modules/Auth/` |
| Backend admin model | `backend/app/base_modules/Admin/model/admin.ts` |
| Auth config | `backend/config/auth.ts` |
## Documentation
| Page | Purpose |
| ------------------------- | ------------------------------------------------------------------------------------- |
| [Frontend](./frontend.md) | AuthProvider, login page, route guard, token storage, and translations. |
| [Backend](./backend.md) | Login/logout/current-user routes, validators, token behavior, and response contracts. |
## Main Capabilities
- Sign in with admin email and password.
- Store bearer token client-side.
- Verify and load current authenticated admin.
- Expose current user, permissions, login, logout, verify, and profile update methods.
- Guard app routes until authentication is known.
- Clear React Query cache on logout.
## Ownership Rules
- Auth owns session state and current user state.
- Auth owns token persistence.
- Auth owns auth route UI and auth guard widget.
- AccountSettings may call AuthProvider `updateUser` but does not own auth state.
- Permission gates consume `AuthUser.permissions` but do not fetch auth state themselves.
+109
View File
@@ -0,0 +1,109 @@
# Auth Backend
The backend Auth module exposes token-based admin authentication routes.
Source root:
```txt
backend/app/base_modules/Auth/
```
## Routes
| Method | Route | Controller Method | Auth Required | Purpose |
| ------ | -------------- | ----------------- | ------------- | ------------------------------------------- |
| `POST` | `/auth/login` | `login` | No | Verify credentials and create bearer token. |
| `POST` | `/auth/logout` | `logout` | Yes | Invalidate current bearer token. |
| `GET` | `/auth/user` | `user` | Yes | Return current admin and permission slugs. |
Route file:
```txt
backend/app/base_modules/Auth/routes.ts
```
## Request Flow
### Login
```txt
POST /auth/login
-> auth_login_validator
-> Admin.findBy('email')
-> hash.verify(password)
-> Admin.accessTokens.create(admin)
-> return { type, token, user }
```
### Current User
```txt
GET /auth/user
-> auth middleware
-> auth.use('api').user
-> load roles
-> query role_permissions
-> return { user, permissions }
```
## Validation
Login validator:
```txt
backend/app/base_modules/Auth/validators/auth_login_validator.ts
```
Current rules:
- `email`: trimmed email string.
- `password`: trimmed string with minimum length 8.
## Token Guard
Auth config:
```txt
backend/config/auth.ts
```
The admin model is configured as the token user provider:
```txt
backend/app/base_modules/Admin/model/admin.ts
```
## Response Contracts
Login response consumed by frontend:
```ts
type LoginResponse = {
type: "bearer";
token: string;
user: unknown;
};
```
Current-user response consumed by frontend:
```ts
type GetUserResponse = {
user: {
id: number;
email: string;
firstName: string;
lastName: string;
status: string;
createdAt: string;
roles: Array<{ id: number; name: string }>;
};
permissions: string[];
};
```
## Ownership Rules
- Auth backend owns token creation, token invalidation, and current-user response shape.
- Admin backend owns admin persistence and profile updates.
- Permission data in `/auth/user` is derived from role-permission rows.
+131
View File
@@ -0,0 +1,131 @@
# Auth Frontend
The frontend module implements authentication screens, session provider, current-user loading, and route guards.
Source root:
```txt
apps/admin/src/core/Auth/
```
## Directory Structure
```txt
Auth/
config/ Routes, messages, translations.
data/
api/ Raw auth/profile API calls and DTOs.
mappers/ Current-user DTO mapping.
stores/ Local token storage helpers.
entities/ Auth and user types.
pages/ Sign-in page.
providers/ AuthProvider and context.
view/
forms/ Sign-in form.
layouts/ Auth layouts.
widgets/ Auth guard.
```
## Routes
Frontend route config:
```txt
config/routing.tsx
```
Mounted module path:
```txt
/auth/*
```
| Route | Page | Purpose |
| -------------- | ------------ | --------------------- |
| `/auth/signin` | `SignInPage` | Admin sign-in screen. |
The index route redirects to `signin`.
## AuthProvider
Provider file:
```txt
providers/AuthProvider/AuthProvider.tsx
```
Exposes:
```ts
auth;
user;
loading;
login(email, password);
logout();
verify();
getUser();
updateUser(payload);
saveAuth(auth);
setUser(user);
```
Important behavior:
- `login` stores the access token, then loads the current user.
- `verify` reloads current user when a token exists.
- `logout` calls the backend, clears auth state, and clears React Query cache.
- `updateUser` calls `/admin/update-profile` and updates current user state.
## API Boundary
Raw API calls:
```txt
data/api/api.ts
data/api/dto.ts
```
| Method | Backend Route | Purpose |
| ------------ | --------------------------- | ---------------------------------- |
| `login` | `POST /auth/login` | Create bearer token. |
| `logout` | `POST /auth/logout` | Invalidate current token. |
| `getUser` | `GET /auth/user` | Load current user and permissions. |
| `updateUser` | `PUT /admin/update-profile` | Update current admin profile. |
Current-user DTO mapping:
```txt
data/mappers/userMapper.ts
```
## Token Storage
Token persistence helpers live in:
```txt
data/stores/localStorage.ts
```
Keep token storage behind these helpers so storage format changes stay local.
## Route Guard
Auth guard widget:
```txt
view/widgets/AuthGuardWidget.tsx
```
Use this widget to protect authenticated app routes. It coordinates auth loading and redirects unauthenticated users to auth routes.
## Translations
Translations are module-local:
```txt
config/messages.ts
config/translation/en.json
config/translation/uk.json
```
Use only `AUTH.*` keys inside this module.
@@ -0,0 +1,47 @@
# Fallback Screens Module
The Fallback Screens module owns route-level and reusable fallback pages in the admin app. It currently provides HTTP-style error screens for forbidden, not found, and server error states, and it is the intended home for future fallback pages such as maintenance, unavailable, empty-state, or generic recovery screens.
## Source Map
| Area | Path |
| --------------------- | ------------------------------------------------------------ |
| Frontend module | `apps/admin/src/core/FallbackScreens/` |
| Frontend routes | `apps/admin/src/core/FallbackScreens/config/routing.tsx` |
| Frontend translations | `apps/admin/src/core/FallbackScreens/config/translation/` |
| Frontend layout | `apps/admin/src/core/FallbackScreens/view/layouts/` |
| Backend module | Not applicable. Fallback Screens is currently frontend-only. |
## Documentation
| Page | Purpose |
| ------------------------- | ----------------------------------------------------------------- |
| [Frontend](./frontend.md) | Routes, page structure, translations, ownership, and conventions. |
| [Backend](./backend.md) | Backend ownership notes and when a backend change may be needed. |
## Main Capabilities
- Render full-page fallback screens under `/error/*`.
- Provide reusable fallback page components for other core modules.
- Keep fallback screen translations module-local.
- Provide a consistent layout for fallback pages.
- Preserve stable public fallback URLs such as `/error/403`, `/error/404`, and `/error/500`.
## Ownership Rules
- FallbackScreens owns fallback page UI, fallback routing, and fallback-specific translations.
- Other modules may import reusable fallback pages, for example permission gates using `Error403Page`.
- Shared API/query error parsing does not belong here; keep it in shared error/query utilities.
- Domain-specific empty states should stay in the owning domain module unless they are generic enough to reuse across modules.
- Backend modules own the actual API status codes and response payloads. FallbackScreens only decides how the admin frontend presents fallback states.
## Common Change Guide
| Change | Start Here |
| ------------------------------ | ------------------------------------------------------------------------------ |
| Add a new fallback route | `apps/admin/src/core/FallbackScreens/config/routing.tsx` |
| Add a new fallback page | `apps/admin/src/core/FallbackScreens/pages/` |
| Change fallback page layout | `apps/admin/src/core/FallbackScreens/view/layouts/FallbackScreensLayout.tsx` |
| Change translations | `apps/admin/src/core/FallbackScreens/config/translation/en.json` and `uk.json` |
| Reuse a fallback in a module | Export it from `apps/admin/src/core/FallbackScreens/index.ts` |
| Change global fallback routing | `apps/admin/src/app/routing/app_routing_setup.tsx` |
@@ -0,0 +1,29 @@
# Fallback Screens Backend
Fallback Screens is currently a frontend-only core module.
There is no backend source folder for this module.
## Backend Relationship
Backend modules may return HTTP status codes or API error payloads that eventually lead the admin frontend to show a fallback screen, but the fallback UI is owned by:
```txt
apps/admin/src/core/FallbackScreens/
```
Examples:
| Status | Frontend fallback | Notes |
| ------ | ---------------------- | --------------------------------------------- |
| `403` | `/error/403` or inline | Used for permission denied states. |
| `404` | `/error/404` | Used for unknown frontend routes. |
| `500` | `/error/500` | Used for generic server failure presentation. |
## When Backend Changes Are Needed
Backend changes are only needed when:
- A backend endpoint should return a different status code.
- A redirect or auth flow should navigate the frontend to a fallback route.
- A feature needs a new machine-readable error contract that the frontend can map to a fallback screen.
@@ -0,0 +1,83 @@
# Fallback Screens Frontend
The frontend module provides fallback UI for app-level error and recovery states.
Source root:
```txt
apps/admin/src/core/FallbackScreens/
```
## Directory Structure
```txt
FallbackScreens/
config/ Routes, messages, and translations.
pages/ Route-level fallback pages.
view/
layouts/ Shared fallback page layout.
```
## Routes
Frontend route config:
```txt
config/routing.tsx
```
Mounted route path:
```txt
/error/*
```
| Route | Page | Purpose |
| ------------ | -------------- | ------------------------------------ |
| `/error` | `Error404Page` | Default fallback route. |
| `/error/403` | `Error403Page` | Forbidden or permission denied page. |
| `/error/404` | `Error404Page` | Not found page. |
| `/error/500` | `Error500Page` | Internal server error page. |
Unknown `/error/*` paths redirect to `/error/404`.
## App Integration
The app-level router mounts `fallbackScreensRoute` from:
```txt
apps/admin/src/app/routing/app_routing_setup.tsx
```
The app-level i18n config merges `fallbackScreensMessages` from:
```txt
apps/admin/src/app/i18n/config.ts
```
## Translations
Translations are module-local:
```txt
config/messages.ts
config/translation/en.json
config/translation/uk.json
```
Use only `FALLBACK_SCREENS.*` keys inside this module.
Do not put fallback page strings in app-level files such as:
```txt
apps/admin/src/app/i18n/messages/en.json
apps/admin/src/app/i18n/messages/uk.json
```
## Page Conventions
- Keep route-level fallback pages under `pages/*Page/index.tsx`.
- Keep shared wrapper layout under `view/layouts/`.
- Keep public URLs stable unless there is a migration plan.
- Prefer module-local translation keys for all visible text, including generic-looking actions like return-home links.
- Add a route only when the fallback is intended to be navigable. Inline-only reusable fallbacks can be exported without a route.
+106
View File
@@ -0,0 +1,106 @@
# File Manager Module
The File Manager module provides authenticated file browsing and file operations for the admin app. It has a backend API for storage operations and a frontend UI for browsing, previewing, uploading, renaming, deleting, duplicating, and editing supported files.
## Source Map
| Area | Path |
| ---------------------- | ------------------------------------------------------------------------------------- |
| Backend module | `backend/app/base_modules/FileManager/` |
| Backend storage config | `backend/config/drive.ts` |
| Backend S3 adapter | `backend/app/base_modules/FileManager/services/storage_service/s3_storage_service.ts` |
| Frontend module | `apps/admin/src/core/FileManager/` |
| Frontend file assets | `apps/admin/public/media/files/` |
| Frontend routes | `apps/admin/src/core/FileManager/config/routing.tsx` |
## Documentation
| Page | Purpose |
| ------------------------- | ---------------------------------------------------------------------------------- |
| [Backend](./backend.md) | API routes, services, storage adapter contract, permissions, and extension points. |
| [Frontend](./frontend.md) | UI structure, React Query and MobX, cache updates, pages, and widgets. |
## Main Capabilities
- Browse folders and files.
- Filter, sort, and paginate directory entries.
- Preview file details and text content for supported extensions.
- Stream raw files for preview or download.
- Create folders.
- Upload files with progress and cancellation support.
- Rename, delete, and duplicate entries.
- Search entries.
- Open an image editor for image files.
- Pick files through reusable frontend picker components.
- Insert files into RichText editor through reusable frontend picker components.
- Use local filesystem storage or S3-compatible storage through Adonis Drive.
## High-Level Flow
```txt
Admin route
-> FileManager frontend page
-> React Query API hook
-> /file-manager backend route
-> FileManagerController
-> FileManagerService
-> StorageService adapter
-> Adonis Drive fs or s3 disk
```
## Data Contract
Backend responses use `Entry` objects:
```ts
type Entry = FileEntry | FolderEntry;
type FileEntry = {
type: "file";
name: string;
path: string;
url: string;
size?: number;
updatedAt?: string;
};
type FolderEntry = {
type: "folder";
name: string;
path: string;
updatedAt?: string;
};
```
The frontend maps DTOs into frontend entities and adds UI-oriented metadata such as `parent` and `extension`.
## Ownership Rules
- Backend owns storage, validation, authorization, path safety, and API response shape.
- Frontend owns navigation, selection, view settings, operation progress, and user interactions.
- React Query owns server state in the frontend.
- MobX stores own local UI and operation state in the frontend.
- Storage adapters must implement `storage_service_interface.ts`; callers should depend on that interface, not a concrete disk implementation.
- The active storage adapter is selected by `DRIVE_DISK`.
## Common Change Guide
| Change | Start Here |
| --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| Add or change an API route | `backend/app/base_modules/FileManager/routes.ts` and `controller/file_manager_controller.ts` |
| Change path validation or naming rules | `services/path_service.ts` |
| Change list filtering, sorting, or pagination | `services/entry_list_service.ts` and frontend query payloads |
| Add or change a storage backend | `services/storage_service/storage_service_interface.ts`, `create_storage_service.ts`, and [Backend storage docs](./backend.md#storage) |
| Add a frontend operation | `apps/admin/src/core/FileManager/data/queries/` and relevant store/widget |
| Add file type icons | `apps/admin/public/media/files/` and `shared/file/fileTypeImportMap.ts` |
| Add preview support | Backend `file_preview_service.ts` and frontend `view/ui/FilePreview/` |
## Planned Work
- Add more non-filesystem storage adapters such as:
- Google Cloud Storage
- Supabase Storage
- Digital Ocean Spaces
- Cloudflare R2
- Add testing notes once module-level backend and frontend tests are established.
- Add libs for rendering large lists.
+368
View File
@@ -0,0 +1,368 @@
# File Manager Backend
The backend module exposes authenticated file operations under `/file-manager`. It validates requests, authorizes actions, normalizes paths, orchestrates file-manager behavior, and delegates raw storage work to a storage adapter.
Source root:
```txt
backend/app/base_modules/FileManager/
```
## Request Flow
```txt
routes.ts
-> controller/file_manager_controller.ts
-> validator/*
-> policy/file_manager_policy.ts
-> services/file_manager_service.ts
-> services/storage_service/storage_service_interface.ts
-> services/storage_service/create_storage_service.ts
-> services/storage_service/fs_storage_service.ts | services/storage_service/s3_storage_service.ts
-> Adonis Drive
```
The controller should stay thin. It handles HTTP concerns: auth policy checks, request validation, path extraction, status codes, and response payload shape.
`FileManagerService` owns business orchestration. Storage adapters own raw persistence operations.
## Routes
All routes are prefixed with `/file-manager` and use `middleware.auth()`.
| Method | Route | Controller Method | Purpose |
| -------- | ------------------ | ----------------- | ------------------------------------------------------- |
| `GET` | `/` | `index` | List root directory entries. |
| `GET` | `/*` | `index` | List entries for a nested directory. |
| `GET` | `/files/details/*` | `show` | Return file metadata and optional text preview content. |
| `GET` | `/files/raw/*` | `raw` | Stream raw file content. |
| `GET` | `/search?q=value` | `search` | Search entries recursively. |
| `POST` | `/folders` | `createFolder` | Create a folder. |
| `POST` | `/files` | `upload` | Upload a file. |
| `POST` | `/files/duplicate` | `duplicate` | Duplicate one or more **file** entries. |
| `PUT` | `/entries/rename` | `rename` | Rename or move an entry within its parent path. |
| `DELETE` | `/*` | `destroy` | Delete an entry. |
## Authorization
Policy file:
```txt
policy/file_manager_policy.ts
```
Controller methods call policy abilities before performing work:
| Ability | Used By |
| ---------- | ---------------------------------------------- |
| `viewList` | List entries, search entries. |
| `view` | File details, raw file stream. |
| `create` | Create folder, upload file, duplicate entries. |
| `update` | Rename entry. |
| `delete` | Delete entry. |
When adding a new operation, add or reuse an explicit policy ability before adding service logic.
## Validation
Validators live in:
```txt
validator/
```
Current validators:
| File | Purpose |
| -------------------------------- | ----------------------------------------------------------------- |
| `list_validator.ts` | Validates filter, sort, order, max results, and pagination token. |
| `create_directory_validator.ts` | Validates parent path and folder name. |
| `rename_entry_validator.ts` | Validates source path and new name. |
| `upload_file_validator.ts` | Validates upload path and file payload. |
| `duplicate_entries_validator.ts` | Validates duplicate source paths. |
| `schemas.ts` | Shared validation schema helpers. |
Path extraction from wildcard routes goes through:
```txt
helpers/get_validated_path_from_request.ts
```
Keep path validation centralized. Avoid parsing wildcard paths directly in controllers or storage adapters.
## Services
### `file_manager_service.ts`
Main orchestration service.
Responsibilities:
- List directory entries with filtering, sorting, and pagination.
- Compose file detail responses with preview content.
- Create directory response entries.
- Validate and move entries.
- Generate unique upload names.
- Generate duplicate copy names.
- Search entries.
- Coordinate storage adapter calls.
### `path_service.ts`
Shared path and filename utility service.
Responsibilities:
- Convert client paths to storage paths.
- Convert storage paths to client paths.
- Join paths safely.
- Get dirname and basename.
- Parse file extension metadata.
- Sanitize filenames.
- Generate unique names.
Use this service for path behavior instead of string manipulation in feature code.
### `entry_list_service.ts`
Applies list behavior to raw storage entries.
Responsibilities:
- Filter entries by category.
- Sort entries.
- Keep folders before files.
- Paginate entries.
- Encode and decode pagination tokens.
Frontend list controls must stay compatible with this service's accepted filter and sort values.
### `file_preview_service.ts`
Adds text preview content to previewable files.
Previewable text extensions are currently defined in:
```txt
services/file_preview_service.ts
```
If a file type needs rich frontend preview, keep the backend response small and stream raw content through `/files/raw/*` when possible.
### `entry_search_service.ts`
Searches entries by path and includes matching parent folders so the frontend can display useful context.
Search receives an abort signal from the controller. Long-running search implementations should respect that signal.
## Storage
Storage abstractions live in:
```txt
services/storage_service/
```
### `storage_service_interface.ts`
Defines the storage adapter contract. Adapters expose raw operations:
- List directory entries.
- List recursive entries.
- Check entry existence.
- Get file metadata.
- Read file content.
- Stream file content.
- Write file.
- Create directory.
- Move entry.
- Delete entry.
- Copy entry.
Services should call the interface, not a concrete adapter.
### `fs_storage_service.ts`
Filesystem storage adapter.
Responsibilities:
- Call the Adonis Drive filesystem disk.
- Map filesystem objects to File Manager entries.
- Handle file streams and metadata.
- Create, move, copy, and delete filesystem entries.
- Preserve filesystem directory marker behavior.
### `s3_storage_service.ts`
S3-compatible storage adapter.
Responsibilities:
- Call the Adonis Drive `s3` disk.
- Map S3 objects and common prefixes to File Manager entries.
- Upload multipart files by streaming the temporary upload file into S3 with `putStream`.
- Read file content and streams from S3.
- Create folders with directory marker objects.
- Move folders by moving every object under the folder prefix.
- Delete files with exact object deletion and folders with folder-prefix deletion.
- Copy file entries.
S3 has no native directories. The adapter uses the same `.keep` marker convention as the filesystem adapter so empty folders can exist.
Folder operations must use slash-terminated prefixes. For example, deleting `test/` must not delete objects such as `test123.txt`.
### `fs_directory_marker_service.ts`
Encapsulates the filesystem directory marker convention.
Current marker:
```txt
.keep
```
Do not duplicate marker logic outside this service. Storage adapters that need virtual folders should use this marker service behind the storage interface.
### `create_storage_service.ts`
Storage service factory.
Current supported storage:
```txt
fs
s3
```
The active adapter is selected by `DRIVE_DISK`.
```env
DRIVE_DISK=fs
```
or:
```env
DRIVE_DISK=s3
```
## Drive Configuration
Adonis Drive config lives in:
```txt
backend/config/drive.ts
```
Configured disks:
| Disk | Driver | Purpose |
| ---- | ------------- | ---------------------------------------------------- |
| `fs` | `services.fs` | Local development and filesystem-backed storage. |
| `s3` | `services.s3` | S3-compatible object storage, including local MinIO. |
The S3 disk expects these environment variables:
| Variable | Local Docker Value | Purpose |
| ----------------------- | --------------------------------------------------------------------- | --------------------------------------------- |
| `AWS_ACCESS_KEY_ID` | `minioadmin` | S3 access key. |
| `AWS_SECRET_ACCESS_KEY` | `minioadmin` | S3 secret key. |
| `AWS_REGION` | `us-east-1` | S3 region. |
| `S3_BUCKET` | `crux-drive` | Bucket used by the file manager. |
| `S3_ENDPOINT` | `http://minio:9000` in Docker, `http://localhost:9000` outside Docker | S3 API endpoint. |
| `S3_CDN_URL` | `http://localhost:9000/crux-drive/` | Browser-facing public URL base for file URLs. |
For MinIO, the S3 disk uses path-style addressing:
```ts
forcePathStyle: true;
```
The adapter also disables ACL API calls:
```ts
supportsACL: false;
```
Bucket public-read behavior is handled by the Docker bucket setup container.
## Local S3 Simulation
Local S3 is simulated with MinIO in:
```txt
docker-compose.yml
```
Services:
| Service | Purpose |
| --------------------- | --------------------------------------------------------------------------------------- |
| `minio` | Runs the S3-compatible object storage server. |
| `minio_create_bucket` | One-shot setup container that creates the bucket and enables anonymous download access. |
MinIO console:
```txt
http://localhost:9001
```
Default local credentials:
```txt
user: minioadmin
pass: minioadmin
```
The `minio_create_bucket` container is expected to exit after setup. A stopped container with successful bucket logs is normal.
To rebuild after dependency or Dockerfile changes:
```sh
docker compose build --no-cache
docker compose up
```
## Response Shape
Directory and file operations return `Entry` objects:
```ts
interface EntryBase {
name: string;
path: string;
updatedAt?: string;
}
interface FileEntry extends EntryBase {
type: "file";
size?: number;
url: string;
}
interface FolderEntry extends EntryBase {
type: "folder";
}
```
List responses include pagination metadata:
```ts
{
data: Entry[];
meta: {
nextPaginationToken: string | null;
};
}
```
## Backend Change Checklist
Before changing backend behavior:
1. Confirm the policy ability for the operation.
2. Add or update a validator for all request input.
3. Keep client paths and storage paths separated through `path_service.ts`.
4. Update the frontend DTO or mapper if response shape changes.
5. Update this documentation when routes, permissions, storage contracts, or response shapes change.
+439
View File
@@ -0,0 +1,439 @@
# File Manager Frontend
The frontend module implements the admin file manager UI. It provides file browsing, previews, uploads, deletion, duplication, renaming, image editing, settings, and reusable file picker widgets.
This module follows the general [React frontend architecture](../../apps-architecture/react-frontend/Readme.md) inside its own module boundary.
Source root:
```txt
apps/admin/src/core/FileManager/
```
## Main Principle
React Query owns server state. MobX owns local UI and operation state.
Server state examples:
- Directory entries.
- File details.
- Raw file blobs.
- Search results.
- Create, rename, delete, duplicate, and upload mutations.
Local state examples:
- Active path.
- Selected paths.
- Expanded tree paths.
- Filter and sort controls.
- Dialog open state.
- Upload and delete operation progress.
- Cached lookup of already loaded entries for UI behavior.
## Directory Structure
```txt
FileManager/
config/ Routes, menu entries, messages, translations.
data/
api/ Raw HTTP methods and DTOs.
hooks/ Feature hooks that combine queries with stores.
mappers/ DTO-to-entity mapping.
queries/ React Query hooks, keys, mutations, and cache helpers.
stores/ MobX local state stores.
entities/ Domain entities and settings types.
providers/ Feature providers and context hooks.
pages/ Route-level screens.
shared/ FileManager utilities that do not depend on view code.
view/ UI components, widgets, forms, and layouts.
```
## Routes
Frontend route config:
```txt
config/routing.tsx
```
Mounted module path:
```txt
/file-manager/*
```
Current child routes:
| Route | Page | Purpose |
| -------------------------------- | ------------------- | ----------------------------------- |
| `/file-manager/drive/explorer/*` | `DriveExplorerPage` | Browse folders and files. |
| `/file-manager/drive/viewer/-/*` | `DriveViewerPage` | View file details and preview. |
| `/file-manager/drive/settings` | `DriveSettingsPage` | Configure file-manager UI settings. |
| `/file-manager/editor/image/-/*` | `ImageEditorPage` | Edit image files. |
Path helpers live in:
```txt
shared/routing/driveLinks.ts
shared/routing/openEntryNewTab.ts
shared/path/leadingSlash.ts
```
Use these helpers instead of manually building file-manager URLs.
## API Boundary
Raw backend calls are centralized in:
```txt
data/api/api.ts
data/api/dto.ts
```
The frontend calls the backend routes documented in [backend.md](./backend.md):
| API Method | Backend Route |
| ----------------- | ------------------------------------ |
| `getEntries` | `GET /file-manager/*` |
| `getFile` | `GET /file-manager/files/details/*` |
| `getRawFile` | `GET /file-manager/files/raw/*` |
| `createDirectory` | `POST /file-manager/folders` |
| `renameEntry` | `PUT /file-manager/entries/rename` |
| `removeEntry` | `DELETE /file-manager/*` |
| `uploadFile` | `POST /file-manager/files` |
| `duplicateFiles` | `POST /file-manager/files/duplicate` |
| `searchEntries` | `GET /file-manager/search` |
DTOs should be mapped before they reach UI code. Entity types live in:
```txt
entities/entry.ts
data/mappers/entryMapper.ts
```
## Data Flow
### Reading Entries
Directory entries are fetched through:
```ts
useGetEntries();
```
The higher-level hook:
```ts
useEntries();
```
does three things:
1. Builds the query payload from `ExplorerStore`.
2. Fetches entries with React Query.
3. Syncs loaded entries into `EntriesStore`.
`EntriesStore` is not the fetch owner. It is an index of loaded entries used by the sidebar tree, selection lookups, active entry lookup, and operation state.
### Deep Links
When opening a nested folder directly, `useEntries()` preloads ancestor folders through React Query. This keeps the sidebar tree and entry index usable for paths like:
```txt
/folder/subfolder/current
```
### Mutations
Mutations use React Query hooks:
- `useCreateDirectory`
- `useRenameEntry`
- `useDeleteEntry`
- `useDuplicateFiles`
- `useUploadFile`
After a successful mutation, the UI updates local cache and stores directly. It does not invalidate entries queries after every operation.
This avoids unnecessary refetches when many items are created, deleted, or uploaded in sequence.
## Query Cache Updates
Entries cache helpers live in:
```txt
data/queries/cache.ts
```
Current helpers:
```ts
addEntriesToCache(parentPath, entries);
removeEntriesFromCache(paths);
renameEntryInCache(prevPath, renamedEntry);
```
These helpers update existing React Query cache data in memory. They do not trigger a network request.
Important behavior:
- Added entries are inserted into cached lists for the matching parent folder.
- Added entries are filter-aware.
- Deleted entries are removed from all cached entry pages.
- Renamed entries are updated across cached entry pages.
Tradeoff: because the module avoids refetching, exact backend sort order may not be perfect after local cache insertion until the folder is naturally refetched or reloaded.
## Stores
### `EntriesStore`
File:
```txt
data/stores/entriesStore.ts
```
Purpose:
- Keep an index of loaded entries by path.
- Provide child lookup by parent path.
- Support selected-entry and active-entry lookup.
- Support local remove and rename updates for already loaded subtrees.
It should not fetch data.
### `ExplorerStore`
File:
```txt
data/stores/explorerStore.ts
```
Purpose:
- Active path.
- Selected paths.
- Expanded tree paths.
- Breadcrumbs.
- Entries filter.
- Sort field and direction.
### `DriveDialogStore`
File:
```txt
data/stores/driveDialogStore.ts
```
Purpose:
- Create-directory dialog state.
- Rename-entry dialog state.
- Current entry selected for rename.
### `UploadOperationStore`
File:
```txt
data/stores/uploadOperationStore.ts
```
Purpose:
- Upload queue.
- Upload progress.
- Upload retry, remove, and abort.
- Upload operation card visibility.
It receives the React Query mutation function from the view layer and manages only operation state.
### `DeleteOperationStore`
File:
```txt
data/stores/deleteOperationStore.ts
```
Purpose:
- Delete queue.
- Delete progress.
- Delete retry and remove.
- Delete operation card visibility.
It receives the React Query mutation function from the view layer and manages only operation state.
### `SettingsStore`
File:
```txt
data/stores/settingsStore.ts
```
Purpose:
- Persisted view mode.
- Compact mode.
- Sidebar visibility.
- Grid size and related display preferences.
## Providers
`DriveProvider` creates and exposes feature stores:
```ts
entriesStore;
explorerStore;
driveDialogStore;
uploadOperationStore;
deleteOperationStore;
```
Use:
```ts
const { entriesStore, explorerStore } = useDrive();
```
`SettingsProvider` owns persisted settings such as view mode, compact mode, sidebar visibility, and grid size.
## Pages And Widgets
### `DriveExplorerPage`
Main file browser page. Composes:
- `DriveProvider`
- Explorer toolbar.
- Explorer content.
- Explorer sidebar.
- Footer.
- Operation watcher.
### `DriveViewerPage`
File details and preview page. Uses:
```ts
useGetFile();
```
### `DriveSettingsPage`
Settings page for persisted file-manager UI preferences.
### `ImageEditorPage`
Image editor page. Uses:
```ts
useGetFile();
useGetRawFile();
useUploadFile();
```
Image editor config and helpers live in the widget layer:
```txt
view/widgets/ImageEditor/config.ts
view/widgets/ImageEditor/helpers.ts
```
This prevents `view` code from importing from `pages`.
### `FilePickerDialog`
Reusable picker widgets live in:
```txt
view/widgets/FilePickerDialog/
```
Keep picker behavior feature-scoped, but pass reusable UI state through props and context instead of coupling low-level UI components to page-level stores.
## View Layer Rules
Feature widgets may use File Manager providers such as `useDrive()` and `useSettings()`.
Lower-level reusable UI components should stay prop-driven. Examples:
- `ExplorerFileEntryGrid`
- `ExplorerFolderEntryList`
- `ExplorerToolbarFilterDropdown`
- `FileManagerFileIcon`
- `FilePreview`
Do not import backend DTOs into view components. View components should consume frontend entities and props.
## File Types And Icons
File helpers live in:
```txt
shared/file/
```
File icons live in:
```txt
apps/admin/public/media/files/
```
To add or change an icon:
1. Add the asset under `apps/admin/public/media/files/`.
2. Update `shared/file/fileTypeImportMap.ts`.
3. Confirm `FileManagerFileIcon` renders the expected fallback for unknown extensions.
## Adding A New Query
1. Add or reuse a method in:
```txt
data/api/api.ts
```
2. Add a key in:
```txt
data/queries/keys.ts
```
3. Create a query file:
```txt
data/queries/example.ts
```
4. Map DTOs to entities before returning app-facing data.
Do not import MobX stores or providers inside `data/queries`.
## Adding A New Mutation
1. Add a raw API method in `data/api/api.ts`.
2. Add a mutation hook in `data/queries`.
3. In the consuming widget or operation store flow, call `mutateAsync`.
4. Update `EntriesStore` if lookup state must change.
5. Update `data/queries/cache.ts` helpers if visible query cache must change.
Do not invalidate entries queries after each operation unless a full refetch is intentionally required.
## Frontend Change Checklist
Before changing frontend behavior:
1. Identify whether the data is server state or local UI state.
2. Keep backend calls in `data/api`.
3. Keep React Query hooks free of MobX provider imports.
4. Map DTOs before passing data to view components.
5. Update cache helpers when mutation results should appear immediately.
6. Update backend docs if the frontend change requires a backend contract change.
+46
View File
@@ -0,0 +1,46 @@
# Permission Module
The Permission module provides frontend permission checks and gates. It also corresponds to the backend permission endpoint and permission discovery service used by AccessControl.
## Source Map
| Area | Path |
| -------------------------- | -------------------------------------------------------- |
| Frontend module | `apps/admin/src/core/Permission/` |
| Frontend gates | `apps/admin/src/core/Permission/view/gates/` |
| Frontend hook | `apps/admin/src/core/Permission/hooks/usePermissions.ts` |
| Frontend types | `apps/admin/src/core/Permission/entities/permission.ts` |
| Backend module | `backend/app/base_modules/Permission/` |
| Backend permission service | `backend/app/services/permission_service.ts` |
## Documentation
| Page | Purpose |
| ------------------------- | ------------------------------------------------------------------ |
| [Frontend](./frontend.md) | Permission types, hook, gates, and usage rules. |
| [Backend](./backend.md) | Permission endpoint, permission discovery, and permission configs. |
## Main Capabilities
- Check whether current user has required permission slugs.
- Support `all` and `any` matching modes.
- Render or hide UI actions through `PermissionGate`.
- Protect full pages through `PermissionPageGate`.
- Treat super admin as an all-permissions user.
## High-Level Flow
```txt
AuthProvider current user
-> AuthUser.permissions
-> usePermissions()
-> hasPermission()
-> PermissionGate or PermissionPageGate
```
## Ownership Rules
- Permission frontend owns permission checks and gate components.
- Auth owns loading current user permissions.
- AccessControl owns managing roles and assigning permissions.
- Backend modules own their own permission definitions in `config/permissions.ts`.
+81
View File
@@ -0,0 +1,81 @@
# Permission Backend
The backend Permission module exposes available permission definitions. It is used by AccessControl when assigning permissions to roles.
Source root:
```txt
backend/app/base_modules/Permission/
```
## Route
| Method | Route | Controller Method | Auth Required | Purpose |
| ------ | ------------- | ----------------- | ------------- | -------------------------------------------- |
| `GET` | `/permission` | `index` | Yes | Return all available permission definitions. |
Route file:
```txt
backend/app/base_modules/Permission/routes.ts
```
## Request Flow
```txt
GET /permission
-> auth middleware
-> PermissionController.index
-> getAllPermissions()
-> scan backend module permission configs
-> return PermissionItem[]
```
## Permission Discovery
Permission discovery lives in:
```txt
backend/app/services/permission_service.ts
```
`getAllPermissions()` scans:
```txt
backend/app/base_modules/*/config/permissions.ts
backend/app/modules/*/config/permissions.ts
```
Each config exports permission items:
```ts
type PermissionItem = {
slug: string;
name: string;
};
```
## Current Permission Configs
Core permission definitions currently include:
```txt
backend/app/base_modules/Admin/config/permissions.ts
backend/app/base_modules/Role/config/permissions.ts
backend/app/base_modules/FileManager/config/permissions.ts
```
When adding a feature that needs access control, add permission definitions to the owning backend module's `config/permissions.ts`.
## Admin Permission Slugs
`getAdminPermissionSlugs(admin)` loads an admin's roles and returns a set of permission slugs from `role_permissions`.
This function is used by backend policies to authorize operations.
## Ownership Rules
- Backend modules own their own permission definitions.
- Permission service owns discovering all available permission definitions.
- AccessControl owns assigning permission slugs to roles.
- Auth owns returning the current user's permission slugs to the frontend.
+102
View File
@@ -0,0 +1,102 @@
# Permission Frontend
The frontend module provides reusable permission-checking primitives for the admin app.
Source root:
```txt
apps/admin/src/core/Permission/
```
## Directory Structure
```txt
Permission/
config/ Static module config and messages.
entities/ Permission types.
hooks/ Current-user permission hook.
lib/ Pure permission check function.
view/gates/ React gate components.
```
## Types
Permission types live in:
```txt
entities/permission.ts
```
Key concepts:
```ts
type PermissionCheckMode = "all" | "any";
type RequiredPermissions = string | string[];
```
## Hook
Current-user permission access:
```txt
hooks/usePermissions.ts
```
The hook reads auth state from AuthProvider and returns:
```ts
permissions;
isAdmin;
loading;
```
`isAdmin` is based on the configured super admin id.
## Pure Permission Check
Permission check logic:
```txt
lib/hasPermission.ts
```
Behavior:
- Super admin passes all checks.
- `all` mode requires every permission.
- `any` mode requires at least one permission.
- Empty required permissions should be treated carefully at call sites.
## Gates
### `PermissionGate`
Use for conditional UI controls such as buttons and menu items.
```tsx
<PermissionGate requiredPermissions="admins.update">
<Button>Edit</Button>
</PermissionGate>
```
### `PermissionPageGate`
Use for route-level access protection.
```tsx
<PermissionPageGate requiredPermissions={["admins.view", "role.view"]}>
<AdminListPage />
</PermissionPageGate>
```
If access is denied, it renders the provided fallback or a 403 page.
## Translations
The module has a local message bundle for consistency:
```txt
config/messages.ts
config/translation/en.json
config/translation/uk.json
```
+32
View File
@@ -0,0 +1,32 @@
# Devtools
The admin devtools module contains development-only interfaces for generating business modules, reviewing UI and form components in the sandbox, and managing the local application version.
Source root:
```txt
apps/admin/src/devtools/
```
The frontend module is registered through `apps/admin/src/devtools/config/module.ts`. Its routes, menu, and translations are exposed only when `IS_DEV_MODE` is enabled. All devtools frontend routes are protected routes.
## Documentation
| Area | Purpose | Documentation |
| ---------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------ |
| Module generator | Generates backend and admin frontend modules from a database table and UI configuration | [Module generator](./module-generator/Readme.md) |
| Sandbox | Interactive examples and copyable source snippets for shared UI and form controls | [Sandbox](./sandbox/Readme.md) |
The version manager is part of the same source module but is outside the scope of these guides.
## Frontend Routes
| Route | Page |
| ------------------------------------ | ------------------------------ |
| `/devtools/module-generator` | Module generator |
| `/devtools/module-generator/docs` | Module generator documentation |
| `/devtools/sandbox/ui` | Shared UI component sandbox |
| `/devtools/sandbox/forms` | Composed form-control sandbox |
| `/devtools/module-generator/version` | Version manager |
Devtools module is enabled only in dev mode!
+78
View File
@@ -0,0 +1,78 @@
# Module Generator
The module generator creates a backend module and a matching admin frontend module from an existing PostgreSQL table. It combines database metadata with frontend code builders and Handlebars templates.
The generator is intended to produce a maintainable starting point. Generated authorization rules, validation, relationships, API behavior, labels, translations, and field controls must still be reviewed for the product domain.
## Source Ownership
| Part | Source |
| ------------------------------- | ---------------------------------------------- |
| Generator page and form | `apps/admin/src/devtools/` |
| Copied frontend module template | `apps/admin/src/modules/_template/` |
| Generator HTTP endpoints | `backend/start/routes.ts` |
| Generator controller | `backend/app/controllers/module_controller.ts` |
| Database inspection | `backend/app/services/database_inspector.ts` |
| Backend Handlebars templates | `backend/app/templates/backend/` |
| Frontend Handlebars templates | `backend/app/templates/frontend/` |
See [frontend.md](./frontend.md) for the UI, payload builders, and generated frontend architecture. See [backend.md](./backend.md) for endpoints, database inspection, templates, and generated backend files. If generated files are not loaded or the module is missing from the menu, use the [troubleshooting guide](./troubleshooting.md).
## Prerequisites
1. Run the backend from the `backend/` workspace. The controller resolves backend and frontend destinations relative to `process.cwd()`.
2. Run the admin app with dev mode enabled so the devtools module is registered.
3. Authenticate in the admin app. All generator endpoints use authentication middleware.
4. Create and migrate the source database table before opening the generator. The form reads tables and columns from PostgreSQL `information_schema`.
5. Use a module name compatible with TypeScript identifiers, route segments, permission slugs, and folder names.
## Generation Flow
```mermaid
flowchart LR
A["Select table and assets"] --> B["Configure form and table"]
B --> C["Frontend builders create source strings"]
C --> D["POST /makeModule/generate"]
D --> E["Inspect database metadata"]
E --> F["Copy frontend _template"]
E --> G["Render backend templates"]
F --> H["Write configured frontend files"]
G --> I["Write backend module and tests"]
```
The generator first copies the complete frontend template, then overwrites generated frontend files with the payload content and database-derived entity metadata. Backend files are rendered according to the selected assets.
## Generated Locations
```txt
backend/modules/<ModuleName>/
backend/tests/modules/<ModuleName>/
apps/admin/src/modules/<name>/
```
Frontend modules are discovered automatically through their `config/module.ts` manifest. Backend module routes and API routes are loaded automatically from `backend/modules/` during application startup.
## Important Operational Behavior
- Generation writes directly into both workspaces; it does not provide a preview, transaction, or rollback.
- Existing destination files may be overwritten. Generate into a clean name and inspect the Git diff before keeping the result.
- File writes are synchronous, but schema inspection and Swagger model generation are asynchronous.
- API generation imports the generated model to build its Swagger schema. Select `model` when selecting `api`.
- The frontend module template is always copied. Frontend form and datatable files are populated only when those assets are selected.
- Generated code is not automatically formatted or committed.
## Verification Checklist
After generating a module:
1. Inspect all generated files and the Git diff.
2. Confirm route paths, API prefixes, permission slugs, and module naming.
3. Review nullable fields, dates, passwords, foreign keys, validation, and relationship names.
4. Replace generic labels and translation values.
5. Verify list, create, update, delete, authorization, and validation-error behavior.
6. Run `npm run build:backend` and `npm run build:admin` from the repository root.
7. Run the generated backend tests and add domain-specific coverage.
For restart, module-discovery, permission, and menu problems, see [Troubleshooting](./troubleshooting.md).
The generated frontend structure follows the [React frontend architecture](../../apps-architecture/react-frontend/Readme.md). Backend modules should be reviewed against the [backend architecture](../../apps-architecture/backend/Readme.md).
+134
View File
@@ -0,0 +1,134 @@
# Module Generator Backend
The backend generator exposes authenticated schema-inspection and generation endpoints. It renders backend files from Handlebars templates and writes frontend files using a copied template plus source strings supplied by the admin app.
## Endpoints
Routes are registered in `backend/start/routes.ts`.
| Method | Path | Controller method | Purpose |
| ------ | ---------------------------- | ----------------- | -------------------------------------------------- |
| `GET` | `/makeModule/tables` | `tables` | List eligible public PostgreSQL tables |
| `GET` | `/makeModule/columns/:table` | `columns` | Return columns used to configure form/table fields |
| `POST` | `/makeModule/generate` | `generate` | Generate selected frontend and backend assets |
All three endpoints use `middleware.auth()`.
## Database Inspection
`backend/app/services/database_inspector.ts` reads PostgreSQL `information_schema`.
- Table discovery is limited to the `public` schema.
- framework, authentication, access-control, migration, and PostgreSQL system tables are excluded.
- columns provide `column_name`, `data_type`, and `is_nullable`.
- columns ending in `_id` are treated as belongs-to relationship candidates.
- tables containing a `<table>_id` column are treated as has-many relationship candidates.
- searchable string and numeric columns are derived for generated resource controllers.
Relationship inference is heuristic. Review generated model imports, related class names, property names, and module availability.
## Type Mapping
The controller maps database types separately for Lucid models, Vine validators, and frontend entities.
| Database category | Lucid TypeScript type | Vine type | Frontend type |
| --------------------- | --------------------- | --------- | --------------- |
| integer/numeric types | `number` | `number` | `number` |
| boolean | `boolean` | `boolean` | `boolean` |
| date/timestamp | `DateTime` | `date` | `IsoDateString` |
| other values | `string` | `string` | `string` |
Nullable database columns receive `| null` in generated model/entity types and optional validation where applicable. `created_at`, `updated_at`, and `deleted_at` receive special handling.
## Backend Output
For module name `<name>`, table `<table>`, and derived module class `<ModuleName>`, selected assets are written under:
```txt
backend/modules/<ModuleName>/
├── api/
│ ├── controllers/<entity>_api_controller.ts
│ ├── validators/<entity>_create_api_validator.ts
│ ├── validators/<entity>_update_api_validator.ts
│ └── routes.ts
├── config/permissions.ts
├── controller/<entity>_controller.ts
├── model/<entity>.ts
├── policy/<entity>_policy.ts
├── validators/<entity>_create_validator.ts
├── validators/<entity>_update_validator.ts
└── routes.ts
```
Tests are written under:
```txt
backend/tests/modules/<ModuleName>/
├── <entity>.spec.ts
└── <entity>.api.spec.ts
```
`<entity>` is derived by removing the final character from the selected table name. This assumes a plural table name and must be reviewed for irregular plurals.
## Asset Behavior
### Model
Renders `backend/app/templates/backend/model.hbs` with columns, inferred relationships, searchable fields, and optional password hashing.
### Controller and routes
Renders an authenticated resource controller with list/search/sort/pagination and CRUD actions. `routes.hbs` registers an `apiOnly` resource route and applies authentication.
### Validators
Creates separate create and update Vine validators. Primary keys and timestamp/deletion fields are excluded according to controller rules. Email, password, nullable, and string trimming behavior is inferred from column names and database metadata.
### Policy and permissions
Creates a Bouncer policy with view/create/update/delete checks, creates `config/permissions.ts`, and adds a lazy policy import to `backend/app/policies/main.ts` when missing.
Permission slugs are derived from the module name, for example:
```txt
<name>s.view
<name>s.create
<name>s.update
<name>s.delete
```
Review names that are already plural because policy and frontend metadata derivation are not a general-purpose pluralization system.
### API subtree
Creates a second CRUD controller and route group under `/api/<entity>`, plus API-specific validators and a test. The controller imports the generated model and passes it to `modelToSchema` for Swagger response metadata.
API routes currently do not add authentication middleware in the generated API template. Add the required authentication and authorization explicitly before exposing the route.
## Frontend Writes Performed by the Backend
The backend copies `apps/admin/src/modules/_template` to `apps/admin/src/modules/<name>` and then writes:
- configured form component and form definition;
- configured datatable and list page;
- configured manage trigger, menu, and module manifest;
- generated `config/meta.ts`;
- database-derived `entities/Entity.ts`.
See [frontend.md](./frontend.md) for the complete frontend structure and payload ownership.
## Template Helpers
The controller registers Handlebars helpers for `eq`, `not`, `or`, and `and`. New templates should use the existing render function and receive an explicit context object from the controller.
## Extending the Backend Generator
1. Add or update a Handlebars template under `backend/app/templates/`.
2. Add an explicit destination path in the controller `paths` object.
3. Create its parent directory through the existing path initialization.
4. Render only when the corresponding asset is selected.
5. Pass normalized metadata rather than querying the database inside templates.
6. Add generation tests for nullable values, dates, numeric types, passwords, and irregular names.
7. Verify both backend and frontend builds after generation.
Avoid adding hidden writes. Every generated or modified destination, including shared registries such as `app/policies/main.ts`, should remain visible in the controller flow and documented here.
+137
View File
@@ -0,0 +1,137 @@
# Module Generator Frontend
The frontend generator UI collects module metadata, selected assets, form layout, and datatable configuration. It converts the configuration into TypeScript/TSX source strings and sends them to the backend generator.
## Main Flow
| Responsibility | Source |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------- |
| Page submission, notifications, and mutation callbacks | `apps/admin/src/devtools/pages/ModuleGeneratorPage/index.tsx` |
| Payload assembly | `apps/admin/src/devtools/pages/ModuleGeneratorPage/lib/buildGenerateModulePayload.ts` |
| Form state and initial values | `apps/admin/src/devtools/view/forms/ModuleGeneratorForm/form.ts` |
| Metadata, form, and datatable sections | `apps/admin/src/devtools/view/forms/ModuleGeneratorForm/sections/` |
| Payload DTOs | `apps/admin/src/devtools/data/api/dto.ts` |
| HTTP client | `apps/admin/src/devtools/data/api/index.ts` |
| React Query hooks | `apps/admin/src/devtools/data/queries/` |
| Source-code builders | `apps/admin/src/devtools/data/builders/` |
The page submits through `useGenerateModule`. On success it resets the form and shows a success alert; backend or network errors are normalized through the shared React Query error helpers.
## Metadata and Assets
The required metadata is:
- `name`: frontend folder name, backend module identity, route segment, and permission-slug base.
- `table`: existing PostgreSQL source table.
- `generate`: selected backend and frontend assets.
Supported asset values are defined by `ModuleAsset`:
| Asset | Workspace | Output |
| ------------ | --------- | --------------------------------------------------- |
| `model` | Backend | Lucid model |
| `controller` | Backend | Authenticated resource controller and test |
| `routes` | Backend | Resource route registration |
| `validator` | Backend | Create and update Vine validators |
| `policy` | Backend | Bouncer policy and permission configuration |
| `api` | Backend | API controller, validators, routes, and test |
| `form` | Frontend | Form definition, component, and modal/sheet trigger |
| `datatable` | Frontend | List page table and menu entry |
The default selection includes all backend assets. Form and datatable generation are opt-in.
## Schema Queries
The frontend uses two authenticated read endpoints before generation:
- `GET /makeModule/tables` populates the table selector.
- `GET /makeModule/columns/:table` populates form and datatable field choices.
Table and column requests are cached through React Query. Column queries remain disabled until a table is selected.
## Payload Builders
`buildGenerateModulePayload` delegates source generation to focused builders:
| Builder | Payload field | Generated destination |
| ------------------------------------- | ---------------- | ----------------------------------- |
| `FormTemplateBuilder.buildComponent` | `form` | `view/forms/MainForm/Component.tsx` |
| `FormTemplateBuilder.buildDefinition` | `formDefinition` | `view/forms/MainForm/form.ts` |
| `FormDialogTemplateBuilder` | `formDialog` | `view/widgets/ManageTrigger.tsx` |
| `DataTableTemplateBuilder` | `dataTable` | `view/widgets/DataTable.tsx` |
| `ListPageTemplateBuilder` | `list` | `pages/ListPage/index.tsx` |
| `MenuTemplateBuilder` | `menu` | `config/menu.ts` |
| `ModuleTemplateBuilder` | `module` | `config/module.ts` |
If form generation is disabled, `form`, `formDefinition`, and `formDialog` are `null`. If datatable generation is disabled, `dataTable` and `menu` are `null`, while the list-page builder emits a page without a table.
## Generated Frontend Architecture
```txt
apps/admin/src/modules/<name>/
├── api/
│ ├── create.ts
│ ├── delete.ts
│ ├── detailed.ts
│ ├── keys.ts
│ ├── list.ts
│ └── update.ts
├── config/
│ ├── translation/
│ ├── menu.ts
│ ├── messages.ts
│ ├── meta.ts
│ ├── module.ts
│ └── routing.tsx
├── entities/
│ └── Entity.ts
├── pages/
│ └── ListPage/
│ └── index.tsx
└── view/
├── forms/
│ └── MainForm/
│ ├── Component.tsx
│ ├── constants.ts
│ ├── form.ts
│ ├── index.ts
│ └── submit.ts
├── layouts/
│ └── Layout.tsx
└── widgets/
├── DataTable.tsx
├── DeleteTrigger.tsx
└── ManageTrigger.tsx
```
The `api` directory intentionally has no barrel file. Consumers import each operation directly, for example:
```ts
import { useCreate } from "../../../api/create";
import { invalidateDetailed } from "../../../api/detailed";
import { invalidateList } from "../../../api/list";
import { useUpdate } from "../../../api/update";
```
Each CRUD operation remains isolated in its own file. `keys.ts` owns React Query key builders, while `list.ts` and `detailed.ts` own their cache invalidation helpers.
## Layer Responsibilities
- `entities/Entity.ts` is generated from database columns and is shared by API hooks, forms, and widgets.
- `api/` owns Axios calls, React Query hooks, cache keys, and mutation notifications.
- `pages/` owns route-level composition and permission page gates.
- `view/forms/` owns schema, form values, UI integration, and submission orchestration.
- `view/widgets/` owns stateful table, delete, and manage interactions.
- `config/` integrates routes, menu, translations, metadata, and permissions with the app module registry.
## Extending the Frontend Generator
When adding a new generated frontend artifact:
1. Add its configuration type under `apps/admin/src/devtools/entities/`.
2. Add UI controls to `ModuleGeneratorForm`.
3. Add a focused builder under `data/builders/` if the artifact contains dynamic source.
4. Add the payload field to `GenerateModulePayload` and `buildGenerateModulePayload`.
5. Accept and write the field in `backend/app/controllers/module_controller.ts`.
6. Add the static structure to `apps/admin/src/modules/_template/` when every module needs it.
7. Verify generation with and without the new asset selected.
@@ -0,0 +1,122 @@
# Module Generator Troubleshooting
## Generated Files Exist but the Module Is Not Loaded
Docker file watching does not always restart the backend or rebuild the admin module registry after the generator creates new directories. Existing file changes may be detected while newly generated module files are not.
Restart both application services manually from the repository root:
```bash
docker compose restart
```
Then reload the admin app. If the module still is not loaded, inspect service logs:
```bash
docker compose logs --tail=200 backend admin
```
Check for TypeScript errors, invalid generated imports, route-loading failures, or module-manifest errors. If the containers are not running through Compose, restart the corresponding backend and admin development processes manually.
The restart is necessary because:
- the backend discovers generated route files under `backend/modules/` during startup;
- the admin app discovers `apps/admin/src/modules/**/config/module.ts` through Vite's module registry;
- adding an entirely new directory may not trigger the same reload behavior as editing an existing file.
## Module Loads but Is Missing from the Menu
If generation succeeded and both services restarted, a missing menu item usually indicates a permission mismatch or an invalid menu/module configuration.
### 1. Compare permission slugs
Check the generated backend permissions:
```txt
backend/modules/<ModuleName>/config/permissions.ts
```
Check the frontend permission constants:
```txt
apps/admin/src/modules/<name>/config/meta.ts
```
The view slug must match exactly. For example:
```ts
// backend permissions.ts
{ slug: 'products.view', name: 'Product view' }
// frontend meta.ts
export const MODULE_PERMISSION_VIEW = 'products.view';
```
Also compare the create, update, and delete slugs. Module-name pluralization is generated heuristically, so names that are already plural or have irregular plurals require manual correction.
### 2. Verify the current role owns the permission
The menu filters items using `MODULE_PERMISSION_VIEW`. Confirm that:
- the generated permission is available to the access-control system;
- the current admin role has the generated view permission;
- the role-to-permission assignment uses the same exact slug;
- the session or permission data has been refreshed after changing the role.
### 3. Check menu configuration
Inspect:
```txt
apps/admin/src/modules/<name>/config/menu.ts
```
Confirm that:
- the exported menu factory is valid;
- `path` uses `MODULE_ROOT_PATH`;
- `permissions` contains `MODULE_PERMISSION_VIEW`;
- the file is not empty;
- the configured route exists.
The generator creates the menu content with the Datatable frontend asset. If Datatable was not selected, add a menu implementation manually or keep the module intentionally hidden from navigation.
### 4. Check the module manifest
Inspect:
```txt
apps/admin/src/modules/<name>/config/module.ts
```
The manifest must export a valid `AppModuleConfig`, must not set `enabled: false`, and must expose the intended `menu`, `routes`, and `messages` values. Its module ID should be unique.
### 5. Check route configuration
Inspect:
```txt
apps/admin/src/modules/<name>/config/routing.tsx
```
Confirm that `MODULE_ROOT_PATH`, the menu path, and the generated route all point to the same URL. A visible menu item with a broken route and an invisible menu item are separate problems; verify both configuration files.
## Final Checks
Run both builds to expose generated TypeScript or import errors:
```bash
npm run build:backend
npm run build:admin
```
Then verify:
1. `backend/modules/<ModuleName>/` exists and contains the selected backend assets.
2. `apps/admin/src/modules/<name>/` exists and contains a valid module manifest.
3. Docker services or local development processes were restarted.
4. Backend and frontend permission slugs match.
5. The current role owns the view permission.
6. The menu path matches the frontend route.
If all checks pass, review the backend and admin logs during startup. Module discovery errors are normally reported there.
+19
View File
@@ -0,0 +1,19 @@
# Devtools Sandbox
The sandbox is an interactive development surface for reviewing shared admin UI components and composed form controls. Each example renders the real component and exposes a copyable usage snippet.
## Routes and Source
| Route | Source | Coverage |
| ------------------------- | ------------------------------------------------- | --------------------------------------------- |
| `/devtools/sandbox/ui` | `apps/admin/src/devtools/pages/SandboxUIPage/` | Shared UI primitives and composed UI behavior |
| `/devtools/sandbox/forms` | `apps/admin/src/devtools/pages/SandboxFormsPage/` | Shared composed form controls |
## Maintenance Rules
- Use real shared components; do not duplicate their implementation in devtools.
- Keep examples small enough to show one API or behavior clearly.
- Keep rendered examples and source snippets synchronized.
- Prefer adding edge-state examples when a component has loading, empty, error, disabled, or destructive behavior.
- Do not add backend dependencies to the sandbox unless the example explicitly documents and isolates them.
- Remove sandbox examples when the underlying shared API is removed.
+54
View File
@@ -0,0 +1,54 @@
# Crux Kit Packages
Reusable packages shared across CRUX applications.
## React Hooks
Packages that provide reusable React hooks and frontend utilities.
| Package | Description |
| ------------------------------------------------- | ------------------------------------------------------------ |
| [@crux-kit/hooks](../../packages/hooks/Readme.md) | Collection of reusable React hooks used across applications. |
---
## Utility Functions
General-purpose utility functions and helpers.
| Package | Description |
| ------------------------------------------------- | -------------------------------------------------------------------- |
| [@crux-kit/utils](../../packages/utils/Readme.md) | Common utility functions shared across frontend and backend projects |
---
## Services
Shared service abstractions and implementations.
| Package | Description |
| -------------------------------------------------------------------------- | ------------------------------------------------- |
| [@crux-kit/intl-date](../../packages/services/intl-date/Readme.md) | Date formatting and localization utilities |
| [@crux-kit/intl-name](../../packages/services/intl-name/Readme.md) | Person and organization name formatting utilities |
| [@crux-kit/local-storage](../../packages/services/local-storage/Readme.md) | Local storage abstraction and helpers |
---
## Design System
Packages related to UI and visual consistency.
| Package | Description |
| --------------------------------------------------------------------- | ------------------------------------------ |
| [@crux-kit/keenicon](../../packages/design-system/keenicon/Readme.md) | KeenIcons integration and icon components. |
---
## Adding New Packages
When creating a new package:
1. Add a `Readme.md` file inside the package root.
2. Document installation, usage, API, and examples.
3. Add the package to this index.
4. Link related packages where appropriate.