umpordez 0.0.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/.claude/settings.local.json +16 -0
  2. package/build.mjs +409 -0
  3. package/cli.mjs +87 -109
  4. package/create.mjs +334 -0
  5. package/package.json +2 -7
  6. package/template/.claude/settings.local.json +20 -0
  7. package/template/.editorconfig +11 -0
  8. package/template/.github/copilot-instructions.md +186 -0
  9. package/template/.nvimrc +7 -0
  10. package/template/.vscode/extensions.json +9 -0
  11. package/template/.vscode/settings.json +28 -0
  12. package/template/AGENTS.md +305 -0
  13. package/template/CLAUDE.md +140 -0
  14. package/template/architecture.md +518 -0
  15. package/template/code.md +211 -0
  16. package/template/dev.sh +45 -0
  17. package/template/install.sh +47 -0
  18. package/template/misc/nginx.conf +151 -0
  19. package/template/misc/systemd/{{PROJECT_SLUG}}-api.service +31 -0
  20. package/template/misc/systemd/{{PROJECT_SLUG}}-site-api.service +31 -0
  21. package/template/misc/systemd/{{PROJECT_SLUG}}-site.service +30 -0
  22. package/template/seed.sh +23 -0
  23. package/template/server/.env.sample +40 -0
  24. package/template/server/.prettierrc +4 -0
  25. package/template/server/apps/api/app.ts +42 -0
  26. package/template/server/apps/api/routes/account.ts +98 -0
  27. package/template/server/apps/api/routes/admin.ts +80 -0
  28. package/template/server/apps/api/routes/auth.ts +222 -0
  29. package/template/server/apps/api/routes/files.ts +89 -0
  30. package/template/server/apps/api/routes/index.ts +19 -0
  31. package/template/server/apps/api/routes/user.ts +17 -0
  32. package/template/server/apps/shared/middlewares/demand-account-access.ts +52 -0
  33. package/template/server/apps/shared/middlewares/demand-admin-user.ts +25 -0
  34. package/template/server/apps/shared/middlewares/demand-user.ts +17 -0
  35. package/template/server/apps/shared/middlewares/error-handler.ts +28 -0
  36. package/template/server/apps/shared/middlewares/request-logger.ts +40 -0
  37. package/template/server/apps/shared/middlewares/try-set-user-by-token.ts +26 -0
  38. package/template/server/apps/shared/utils.ts +71 -0
  39. package/template/server/apps/site-api/app.ts +42 -0
  40. package/template/server/apps/site-api/routes/index.ts +10 -0
  41. package/template/server/apps/site-api/routes/public.ts +25 -0
  42. package/template/server/console/index.ts +26 -0
  43. package/template/server/console/runner.ts +67 -0
  44. package/template/server/console/tasks/example.ts +29 -0
  45. package/template/server/console/tasks/seed-admin.ts +71 -0
  46. package/template/server/core/context.ts +49 -0
  47. package/template/server/core/db.ts +12 -0
  48. package/template/server/core/email.ts +87 -0
  49. package/template/server/core/errors.ts +44 -0
  50. package/template/server/core/knexfile.ts +28 -0
  51. package/template/server/core/logger.ts +60 -0
  52. package/template/server/core/models/account.ts +319 -0
  53. package/template/server/core/models/auth.ts +317 -0
  54. package/template/server/core/models/base.ts +19 -0
  55. package/template/server/core/models/user.ts +343 -0
  56. package/template/server/core/s3.ts +183 -0
  57. package/template/server/emails/_styles.css +5 -0
  58. package/template/server/emails/_template.html +28 -0
  59. package/template/server/emails/accountWelcome.html +4 -0
  60. package/template/server/emails/forgetPassword.html +5 -0
  61. package/template/server/eslint.config.js +16 -0
  62. package/template/server/knex.sh +4 -0
  63. package/template/server/migrations/20260208000000_initial-schema.ts +56 -0
  64. package/template/server/migrations/20260208000001_seed-admin.ts +42 -0
  65. package/template/server/migrations/20260208000002_add-user-avatar.ts +13 -0
  66. package/template/server/package.json +58 -0
  67. package/template/server/tsconfig.json +27 -0
  68. package/template/server/types/api.ts +20 -0
  69. package/template/server/types/express.d.ts +35 -0
  70. package/template/ui/admin/.prettierrc +4 -0
  71. package/template/ui/admin/components.json +20 -0
  72. package/template/ui/admin/eslint.config.js +16 -0
  73. package/template/ui/admin/index.html +12 -0
  74. package/template/ui/admin/package.json +57 -0
  75. package/template/ui/admin/postcss.config.js +6 -0
  76. package/template/ui/admin/src/app.tsx +13 -0
  77. package/template/ui/admin/src/components/nav-user.tsx +95 -0
  78. package/template/ui/admin/src/components/theme-provider.tsx +65 -0
  79. package/template/ui/admin/src/components/theme-toggle.tsx +17 -0
  80. package/template/ui/admin/src/components/ui/badge.tsx +36 -0
  81. package/template/ui/admin/src/components/ui/button.tsx +56 -0
  82. package/template/ui/admin/src/components/ui/calendar.tsx +68 -0
  83. package/template/ui/admin/src/components/ui/card.tsx +79 -0
  84. package/template/ui/admin/src/components/ui/checkbox.tsx +28 -0
  85. package/template/ui/admin/src/components/ui/date-picker.tsx +78 -0
  86. package/template/ui/admin/src/components/ui/date-range-picker.tsx +208 -0
  87. package/template/ui/admin/src/components/ui/dialog.tsx +121 -0
  88. package/template/ui/admin/src/components/ui/dropdown-menu.tsx +200 -0
  89. package/template/ui/admin/src/components/ui/input.tsx +22 -0
  90. package/template/ui/admin/src/components/ui/label.tsx +24 -0
  91. package/template/ui/admin/src/components/ui/popover.tsx +29 -0
  92. package/template/ui/admin/src/components/ui/select.tsx +158 -0
  93. package/template/ui/admin/src/components/ui/separator.tsx +29 -0
  94. package/template/ui/admin/src/components/ui/sheet.tsx +136 -0
  95. package/template/ui/admin/src/components/ui/sidebar.tsx +759 -0
  96. package/template/ui/admin/src/components/ui/skeleton.tsx +15 -0
  97. package/template/ui/admin/src/components/ui/sonner.tsx +26 -0
  98. package/template/ui/admin/src/components/ui/switch.tsx +27 -0
  99. package/template/ui/admin/src/components/ui/table.tsx +117 -0
  100. package/template/ui/admin/src/components/ui/tabs.tsx +53 -0
  101. package/template/ui/admin/src/components/ui/textarea.tsx +22 -0
  102. package/template/ui/admin/src/components/ui/tooltip.tsx +28 -0
  103. package/template/ui/admin/src/hooks/queries/use-accounts.ts +68 -0
  104. package/template/ui/admin/src/hooks/queries/use-auth.ts +61 -0
  105. package/template/ui/admin/src/hooks/queries/use-dashboard.ts +70 -0
  106. package/template/ui/admin/src/hooks/queries/use-members.ts +90 -0
  107. package/template/ui/admin/src/hooks/queries/use-users.ts +99 -0
  108. package/template/ui/admin/src/hooks/use-confirm.tsx +92 -0
  109. package/template/ui/admin/src/hooks/use-mobile.tsx +22 -0
  110. package/template/ui/admin/src/hooks/use-sidebar.tsx +4 -0
  111. package/template/ui/admin/src/hooks/use-user.tsx +86 -0
  112. package/template/ui/admin/src/index.css +22 -0
  113. package/template/ui/admin/src/layouts/account.tsx +278 -0
  114. package/template/ui/admin/src/layouts/admin.tsx +182 -0
  115. package/template/ui/admin/src/layouts/public.tsx +9 -0
  116. package/template/ui/admin/src/layouts/user.tsx +107 -0
  117. package/template/ui/admin/src/lib/config.ts +9 -0
  118. package/template/ui/admin/src/lib/fetch-api.ts +101 -0
  119. package/template/ui/admin/src/lib/query-client.ts +12 -0
  120. package/template/ui/admin/src/lib/query-keys.ts +44 -0
  121. package/template/ui/admin/src/lib/utils.ts +24 -0
  122. package/template/ui/admin/src/main.tsx +17 -0
  123. package/template/ui/admin/src/pages/account/dashboard.tsx +128 -0
  124. package/template/ui/admin/src/pages/account/members.tsx +414 -0
  125. package/template/ui/admin/src/pages/account/settings.tsx +210 -0
  126. package/template/ui/admin/src/pages/admin/accounts.tsx +164 -0
  127. package/template/ui/admin/src/pages/admin/dashboard.tsx +243 -0
  128. package/template/ui/admin/src/pages/admin/users.tsx +395 -0
  129. package/template/ui/admin/src/pages/public/error.tsx +24 -0
  130. package/template/ui/admin/src/pages/public/forgot-password.tsx +74 -0
  131. package/template/ui/admin/src/pages/public/login.tsx +102 -0
  132. package/template/ui/admin/src/pages/public/reset-password.tsx +106 -0
  133. package/template/ui/admin/src/pages/public/signup.tsx +118 -0
  134. package/template/ui/admin/src/pages/user/profile.tsx +313 -0
  135. package/template/ui/admin/src/pages/user/select-account.tsx +114 -0
  136. package/template/ui/admin/src/router.tsx +71 -0
  137. package/template/ui/admin/tailwind.config.ts +71 -0
  138. package/template/ui/admin/tsconfig.json +24 -0
  139. package/template/ui/admin/vite.config.ts +15 -0
  140. package/template/ui/site/.env.sample +3 -0
  141. package/template/ui/site/.prettierrc +4 -0
  142. package/template/ui/site/app.ts +50 -0
  143. package/template/ui/site/eslint.config.js +16 -0
  144. package/template/ui/site/package.json +33 -0
  145. package/template/ui/site/styles/input.css +3 -0
  146. package/template/ui/site/tailwind.config.js +8 -0
  147. package/template/ui/site/tsconfig.json +15 -0
  148. package/template/ui/site/views/pages/home.ejs +305 -0
  149. package/template/ui/site/views/partials/footer.ejs +44 -0
  150. package/template/ui/site/views/partials/header.ejs +83 -0
  151. package/template-variables.mjs +186 -0
@@ -0,0 +1,305 @@
1
+ # AGENTS.md
2
+
3
+ ## Project: {{PROJECT_NAME}}
4
+
5
+ {{PROJECT_DESCRIPTION}}
6
+
7
+ Multi-tenant SaaS application built with the **umpordez** architecture pattern for the Brazilian market.
8
+
9
+ ## Quick Start
10
+
11
+ ```bash
12
+ ./install.sh # Install all deps + create .env files from samples
13
+ ./dev.sh # Start all services concurrently (Ctrl+C to stop)
14
+ ```
15
+
16
+ ## Repository Structure
17
+
18
+ Independent packages (each has its own `node_modules` and `package-lock.json`):
19
+
20
+ ```
21
+ server/
22
+ apps/api/ Admin/main API (port 4000)
23
+ routes/ Route handlers by domain (auth, user, account, admin, files)
24
+ apps/site-api/ Site API (port 4001)
25
+ routes/ Public API route handlers
26
+ apps/shared/ Shared across all APIs
27
+ middlewares/ Auth guards, error handler, request logger
28
+ utils.ts buildHandler() async route wrapper
29
+ core/ Shared business logic
30
+ context.ts DI container (fresh per request)
31
+ models/ Data access layer (one model per entity)
32
+ db.ts Knex database wrapper
33
+ knexfile.ts PostgreSQL connection config
34
+ s3.ts AWS S3 uploads via multer-s3
35
+ email.ts Email sending via nodemailer + templates
36
+ errors.ts AppError hierarchy (ValidationError, NotFoundError, etc.)
37
+ logger.ts Application logging
38
+ console/ CLI task runner
39
+ tasks/ One file per task
40
+ migrations/ Knex migrations (raw SQL)
41
+ types/ TypeScript type extensions (express.d.ts)
42
+ emails/ HTML email templates
43
+
44
+ ui/admin/ React SPA (Vite, port 5173)
45
+ src/
46
+ pages/ Route pages by scope: public/, user/, admin/, account/
47
+ layouts/ Layout wrappers: PublicLayout, UserLayout, AdminLayout, AccountLayout
48
+ components/ui/ shadcn/ui components (Radix + CVA + Tailwind)
49
+ components/ App-level components (sidebar, theme, nav)
50
+ hooks/ Custom hooks (use-user, use-confirm, use-mobile, use-sidebar)
51
+ hooks/queries/ React Query hooks per entity (use-users, use-accounts, use-members, etc.)
52
+ lib/ Utilities (fetch-api, query-client, query-keys, config, utils)
53
+
54
+ ui/site/ Public marketing site (Express + EJS, port 3000)
55
+ views/ EJS templates (pages/, partials/)
56
+ styles/ TailwindCSS input
57
+ ```
58
+
59
+ ## Tech Stack
60
+
61
+ - **Backend:** TypeScript, Express.js, PostgreSQL, Knex.js, Zod
62
+ - **Admin UI:** React 18, Vite, TailwindCSS, shadcn/ui (Radix), React Router DOM 6, React Query (TanStack Query v5), React Hook Form + Zod, Recharts
63
+ - **Site UI:** Express + EJS + TailwindCSS
64
+ - **Auth:** httpOnly JWT cookies (30-day expiry)
65
+ - **Uploads:** AWS S3 via multer-s3 with signed URLs
66
+
67
+ ## Code Style
68
+
69
+ - 4-space indentation, semicolons, LF line endings
70
+ - TypeScript strict mode in all packages
71
+ - Code in English, UI text in Portuguese (Brazilian market)
72
+ - Path aliases: `#core/*`, `#shared/*`, `#api/*` (server), `@/*` (admin UI)
73
+
74
+ ### File Naming
75
+ | Type | Convention | Example |
76
+ |---|---|---|
77
+ | Components | PascalCase | `NavUser.tsx` |
78
+ | Hooks | kebab-case with `use-` | `use-user.tsx` |
79
+ | Query hooks | kebab-case with `use-` | `use-accounts.ts` |
80
+ | Utils/Lib | kebab-case | `fetch-api.ts` |
81
+ | Routes | kebab-case | `demand-user.ts` |
82
+ | Models | kebab-case | `account.ts` |
83
+ | Migrations | timestamp prefix | `20260208000000_initial-schema.ts` |
84
+
85
+ ## Critical Patterns
86
+
87
+ ### Context-Based Dependency Injection
88
+
89
+ Every request gets a fresh `Context` (created in middleware). The Context holds all model instances. **ALL data access goes through models — never raw queries in routes.**
90
+
91
+ ```typescript
92
+ // In route handlers:
93
+ const users = await req.context.user.list({ page: 1 });
94
+ const account = await req.context.account.findById(id);
95
+ ```
96
+
97
+ **Adding a new model:**
98
+ 1. Create `server/core/models/my-entity.ts` extending `BaseModel`
99
+ 2. Add to `server/core/context.ts`: `myEntity: MyEntityModel;` + `this.myEntity = new MyEntityModel(this);`
100
+ 3. Access via `req.context.myEntity` or `this.context.myEntity`
101
+
102
+ ### Model Pattern
103
+
104
+ ```typescript
105
+ import BaseModel from './base.js';
106
+ import { NotFoundError, ValidationError } from '#core/errors.js';
107
+
108
+ class MyEntityModel extends BaseModel {
109
+ // this.knex — Knex instance
110
+ // this.context — access other models
111
+ // this.db — Db singleton
112
+
113
+ async list(options = {}) {
114
+ const page = Math.max(1, options.page || 1);
115
+ const limit = Math.min(100, Math.max(1, options.limit || 20));
116
+ const offset = (page - 1) * limit;
117
+
118
+ const baseQuery = this.knex('my_entities').where('account_id', accountId);
119
+ if (options.search?.trim()) {
120
+ baseQuery.whereILike('name', `%${options.search.trim()}%`);
121
+ }
122
+
123
+ const [{ count }] = await baseQuery.clone().count('id as count');
124
+ const total = parseInt(String(count), 10);
125
+ const rows = await baseQuery.clone()
126
+ .select('*').orderBy('utc_created_on', 'desc')
127
+ .limit(limit).offset(offset);
128
+
129
+ return { rows, total, page, limit, totalPages: Math.ceil(total / limit) };
130
+ }
131
+ }
132
+ ```
133
+
134
+ **Key rules:**
135
+ - Always filter by `account_id` in multi-tenant queries
136
+ - Use `PaginatedResult<T>` pattern: `{ rows, total, page, limit, totalPages }`
137
+ - Throw `AppError` subclasses (`ValidationError`, `NotFoundError`, `ConflictError`, `ForbiddenError`)
138
+
139
+ ### Route Handler Pattern
140
+
141
+ ```typescript
142
+ import { buildHandler } from '#shared/utils.js';
143
+ import { z } from 'zod';
144
+
145
+ const createSchema = z.object({ name: z.string().min(1, 'Nome é obrigatório') });
146
+
147
+ async function handleCreate(req: Request, res: Response): Promise<void> {
148
+ const data = createSchema.parse(req.body);
149
+ const result = await req.context.myEntity.create(req.accountId!, data);
150
+ res.json({ ok: true, result });
151
+ }
152
+
153
+ router.post('/my-entities', buildHandler(handleCreate));
154
+
155
+ export default function makeEndpoint(app: Express): void {
156
+ app.use('/api/account/:accountId',
157
+ trySetUserByTokenMiddleware,
158
+ demandUserMiddleware,
159
+ demandAccountAccessMiddleware,
160
+ router
161
+ );
162
+ }
163
+ ```
164
+
165
+ ### Middleware Chain
166
+
167
+ ```
168
+ requestLogger → contextInjection → trySetUserByToken → [demand*] → routes → errorHandler
169
+ ```
170
+
171
+ | Middleware | Does |
172
+ |---|---|
173
+ | `try-set-user-by-token` | Reads cookie, sets `req.user` if valid |
174
+ | `demand-user` | Requires `req.user`, returns 401 |
175
+ | `demand-admin-user` | Requires admin role, returns 403 |
176
+ | `demand-account-access` | Checks account access, sets `req.accountId` + `req.accountRole` |
177
+
178
+ ### Error Classes
179
+
180
+ ```typescript
181
+ import { ValidationError, NotFoundError, ConflictError, ForbiddenError } from '#core/errors.js';
182
+
183
+ throw new ValidationError('A senha deve ter pelo menos 6 caracteres'); // 400
184
+ throw new NotFoundError('Usuário não encontrado'); // 404
185
+ throw new ConflictError('Este email já está em uso'); // 409
186
+ throw new ForbiddenError('Acesso negado'); // 403
187
+ ```
188
+
189
+ ### Transactions
190
+
191
+ ```typescript
192
+ await req.context.knexTransaction(async () => {
193
+ const user = await req.context.user.create(data);
194
+ await req.context.account.addMember(accountId, { ... });
195
+ // If anything throws, everything rolls back
196
+ });
197
+ ```
198
+
199
+ ### Migrations
200
+
201
+ Always use raw SQL:
202
+ ```typescript
203
+ export async function up(knex: Knex): Promise<void> {
204
+ await knex.schema.raw(`
205
+ CREATE TABLE my_entities (
206
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
207
+ account_id UUID NOT NULL REFERENCES accounts(id),
208
+ name VARCHAR(255) NOT NULL,
209
+ utc_created_on TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
210
+ utc_updated_on TIMESTAMP WITH TIME ZONE DEFAULT NOW()
211
+ );
212
+ CREATE INDEX idx_my_entities_account ON my_entities(account_id);
213
+ `);
214
+ }
215
+ ```
216
+
217
+ Conventions: always include `id` (UUID), `utc_created_on`, `utc_updated_on`. Filename: `YYYYMMDDHHMMSS_description.ts`.
218
+
219
+ ## Frontend Patterns
220
+
221
+ ### fetchApi
222
+
223
+ ```typescript
224
+ import fetchApi from '@/lib/fetch-api';
225
+ const data = await fetchApi('user/accounts'); // GET
226
+ const data = await fetchApi('admin/users', { page: 1 }, 'GET'); // GET with params
227
+ const data = await fetchApi('login', { email, password }, 'POST'); // POST
228
+ const data = await fetchApi(`admin/users/${id}`, null, 'DELETE'); // DELETE
229
+ ```
230
+
231
+ Uses `credentials: 'include'` for cookie auth. On 401/403: clears storage, redirects to `/login`.
232
+
233
+ ### React Query Hooks
234
+
235
+ All server state uses TanStack Query v5 with a structured query key factory in `lib/query-keys.ts`.
236
+
237
+ ```typescript
238
+ export function useMyEntities(accountId: string, filters: PaginationFilters) {
239
+ return useQuery({
240
+ queryKey: queryKeys.myEntities.list(accountId, filters),
241
+ placeholderData: keepPreviousData, // Prevents UI flash during search
242
+ queryFn: async () => {
243
+ const res = await fetchApi(`account/${accountId}/my-entities`, filters);
244
+ if (!res.ok) throw new Error(res.message);
245
+ return res;
246
+ },
247
+ });
248
+ }
249
+ ```
250
+
251
+ **Key rules:**
252
+ - Use `keepPreviousData` on paginated/searchable queries
253
+ - Invalidate related queries in mutation `onSuccess`
254
+ - Add keys to `lib/query-keys.ts`
255
+
256
+ ### Layouts and Routing
257
+
258
+ | Layout | Auth | Route prefix |
259
+ |---|---|---|
260
+ | `PublicLayout` | None | `/` |
261
+ | `UserLayout` | `useUser()` | `/user` |
262
+ | `AdminLayout` | admin role | `/admin` |
263
+ | `AccountLayout` | account access | `/account/:accountId` |
264
+
265
+ ### Confirm Modal
266
+
267
+ Use `useConfirm()` instead of `window.confirm()`:
268
+ ```typescript
269
+ const confirm = useConfirm();
270
+ const ok = await confirm({
271
+ title: 'Excluir Item',
272
+ description: `Tem certeza que deseja excluir "${item.name}"?`,
273
+ confirmLabel: 'Excluir',
274
+ variant: 'destructive',
275
+ });
276
+ if (!ok) return;
277
+ ```
278
+
279
+ ## Multi-Tenancy
280
+
281
+ ```
282
+ users (global identity) → user_in_accounts (many-to-many + role) → accounts (tenant) → [domain entities]
283
+ ```
284
+
285
+ **Roles:** `admin` (platform-wide), `owner` (account owner), `manager`, `user`
286
+
287
+ ## Commands
288
+
289
+ ```bash
290
+ # Server (from server/)
291
+ npm run dev # Admin API with hot-reload
292
+ npm run dev:site-api # Site API with hot-reload
293
+ npm run migrate:latest # Run pending migrations
294
+ npm run migrate:rollback # Rollback last batch
295
+ npm run migrate:make NAME # Create new migration
296
+ npm run console -- --task=TASK_NAME # Run console task
297
+
298
+ # Admin UI (from ui/admin/)
299
+ npm run dev # Vite dev server on port 5173
300
+ npm run build # TypeScript check + Vite production build
301
+
302
+ # Site UI (from ui/site/)
303
+ npm run dev # Express server + Tailwind watcher
304
+ npm run build # TypeScript + minified Tailwind CSS
305
+ ```
@@ -0,0 +1,140 @@
1
+ # CLAUDE.md
2
+
3
+ ## Project: {{PROJECT_NAME}}
4
+
5
+ {{PROJECT_DESCRIPTION}}
6
+
7
+ Multi-tenant SaaS application built with the **umpordez**
8
+ architecture pattern for the Brazilian market.
9
+
10
+ **Before writing code, read these two files:**
11
+ - `code.md` — coding standards, style rules, naming conventions
12
+ - `architecture.md` — architecture patterns, how-tos, examples
13
+
14
+ ## Quick Start
15
+
16
+ ```bash
17
+ ./install.sh # Install all deps + create .env files
18
+ ./seed.sh # Create database + migrate + seed admin
19
+ ./dev.sh # Start all services (Ctrl+C to stop)
20
+ ```
21
+
22
+ ## Repository Structure
23
+
24
+ Independent packages (each has its own `node_modules`):
25
+
26
+ ```
27
+ server/
28
+ apps/api/ Admin/main API (port {{API_PORT}})
29
+ routes/ Route handlers (auth, user, account, admin, files)
30
+ apps/site-api/ Site/public API (port {{SITE_API_PORT}})
31
+ routes/ Public API route handlers
32
+ apps/shared/ Shared across all APIs
33
+ middlewares/ Auth guards, error handler, request logger
34
+ utils.ts buildHandler() async route wrapper
35
+ core/ Shared business logic
36
+ context.ts DI container (fresh per request)
37
+ models/ Data access layer (one model per entity)
38
+ db.ts Knex database wrapper
39
+ knexfile.ts PostgreSQL connection config
40
+ s3.ts AWS S3 uploads via multer-s3
41
+ email.ts Email sending via nodemailer + templates
42
+ errors.ts AppError hierarchy
43
+ logger.ts Application logging
44
+ console/ CLI task runner
45
+ tasks/ One file per task
46
+ migrations/ Knex migrations (raw SQL)
47
+ types/ TypeScript type extensions (express.d.ts)
48
+ emails/ HTML email templates
49
+
50
+ ui/admin/ React SPA (Vite, port {{ADMIN_UI_PORT}})
51
+ src/
52
+ pages/ Route pages: public/, user/, admin/, account/
53
+ layouts/ PublicLayout, UserLayout, AdminLayout, AccountLayout
54
+ components/ui/ shadcn/ui components (Radix + CVA + Tailwind)
55
+ components/ App-level components (sidebar, theme, nav)
56
+ hooks/ Custom hooks (use-user, use-confirm, etc.)
57
+ hooks/queries/ React Query hooks per entity
58
+ lib/ Utilities (fetch-api, query-client, query-keys, utils)
59
+
60
+ ui/site/ Public marketing site (Express + EJS, port {{SITE_PORT}})
61
+ views/ EJS templates (pages/, partials/)
62
+ styles/ TailwindCSS input
63
+ ```
64
+
65
+ ## Commands
66
+
67
+ ### Server (from `server/`)
68
+ ```bash
69
+ npm run dev # Admin API with hot-reload (tsx watch)
70
+ npm run dev:site-api # Site API with hot-reload
71
+ npm run build # TypeScript compilation
72
+ npm run migrate:latest # Run pending migrations
73
+ npm run migrate:rollback # Rollback last batch
74
+ npm run migrate:make NAME # Create new migration
75
+ npm run console # List console tasks
76
+ npm run console -- --task=TASK_NAME # Run specific task
77
+ ```
78
+
79
+ ### Admin UI (from `ui/admin/`)
80
+ ```bash
81
+ npm run dev # Vite dev server on port {{ADMIN_UI_PORT}}
82
+ npm run build # TypeScript check + Vite production build
83
+ npm run lint # ESLint check
84
+ ```
85
+
86
+ ### Site UI (from `ui/site/`)
87
+ ```bash
88
+ npm run dev # Express server + Tailwind watcher
89
+ npm run build # TypeScript + minified Tailwind CSS
90
+ ```
91
+
92
+ ### Production Build (via umpordez CLI)
93
+ ```bash
94
+ umpordez build ../app ../builds # Build all services for deployment
95
+ ```
96
+
97
+ ## Tech Stack
98
+
99
+ - **Backend:** TypeScript, Express.js, PostgreSQL, Knex.js, Zod
100
+ - **Admin UI:** React 18, Vite, TailwindCSS, shadcn/ui (Radix),
101
+ React Router DOM 6, React Query v5, React Hook Form + Zod
102
+ - **Site UI:** Express + EJS + TailwindCSS
103
+ - **Auth:** httpOnly JWT cookies (30-day expiry)
104
+ - **Uploads:** AWS S3 via multer-s3 with signed URLs
105
+ - **Email:** Nodemailer with HTML templates (lodash)
106
+
107
+ ## Key Rules (summary — see code.md for full list)
108
+
109
+ - **Always use braces** for `if`/`else`/`for`/`while`
110
+ - **Never silently ignore errors** — every `catch` uses
111
+ `logger.error()`
112
+ - **Max 80 columns** — break long lines
113
+ - **S3 files**: store `key` in DB, serve via signed URLs only
114
+ - **Charts**: pure CSS/Tailwind bars with Radix Tooltip
115
+ - Code in **English**, UI text in **Portuguese**
116
+ - Path aliases: `#core/*`, `#shared/*`, `#api/*` (server),
117
+ `@/*` (admin UI)
118
+
119
+ ## Deployment
120
+
121
+ ### Production Structure
122
+ ```
123
+ /apps/{{PROJECT_SLUG}}/
124
+ ├── builds/server/ # npm run build output
125
+ ├── builds/ui/admin/dist/ # vite build (served by nginx)
126
+ ├── builds/ui/site/ # compiled site
127
+ ├── log/ # service logs
128
+ └── .env # production env vars
129
+ ```
130
+
131
+ ### Nginx
132
+ Reverse proxy for all services:
133
+ - `api.{{DOMAIN}}` → Admin API ({{API_PORT}})
134
+ - `api-site.{{DOMAIN}}` → Site API ({{SITE_API_PORT}})
135
+ - `app.{{DOMAIN}}` → Admin Panel (static, `try_files`)
136
+ - `{{DOMAIN}}` → Public Site ({{SITE_PORT}})
137
+
138
+ ### Systemd
139
+ Service files in `misc/systemd/` for each Node.js process. Admin
140
+ Panel is static — no service needed.