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.
- package/.claude/settings.local.json +16 -0
- package/build.mjs +409 -0
- package/cli.mjs +87 -109
- package/create.mjs +334 -0
- package/package.json +2 -7
- package/template/.claude/settings.local.json +20 -0
- package/template/.editorconfig +11 -0
- package/template/.github/copilot-instructions.md +186 -0
- package/template/.nvimrc +7 -0
- package/template/.vscode/extensions.json +9 -0
- package/template/.vscode/settings.json +28 -0
- package/template/AGENTS.md +305 -0
- package/template/CLAUDE.md +140 -0
- package/template/architecture.md +518 -0
- package/template/code.md +211 -0
- package/template/dev.sh +45 -0
- package/template/install.sh +47 -0
- package/template/misc/nginx.conf +151 -0
- package/template/misc/systemd/{{PROJECT_SLUG}}-api.service +31 -0
- package/template/misc/systemd/{{PROJECT_SLUG}}-site-api.service +31 -0
- package/template/misc/systemd/{{PROJECT_SLUG}}-site.service +30 -0
- package/template/seed.sh +23 -0
- package/template/server/.env.sample +40 -0
- package/template/server/.prettierrc +4 -0
- package/template/server/apps/api/app.ts +42 -0
- package/template/server/apps/api/routes/account.ts +98 -0
- package/template/server/apps/api/routes/admin.ts +80 -0
- package/template/server/apps/api/routes/auth.ts +222 -0
- package/template/server/apps/api/routes/files.ts +89 -0
- package/template/server/apps/api/routes/index.ts +19 -0
- package/template/server/apps/api/routes/user.ts +17 -0
- package/template/server/apps/shared/middlewares/demand-account-access.ts +52 -0
- package/template/server/apps/shared/middlewares/demand-admin-user.ts +25 -0
- package/template/server/apps/shared/middlewares/demand-user.ts +17 -0
- package/template/server/apps/shared/middlewares/error-handler.ts +28 -0
- package/template/server/apps/shared/middlewares/request-logger.ts +40 -0
- package/template/server/apps/shared/middlewares/try-set-user-by-token.ts +26 -0
- package/template/server/apps/shared/utils.ts +71 -0
- package/template/server/apps/site-api/app.ts +42 -0
- package/template/server/apps/site-api/routes/index.ts +10 -0
- package/template/server/apps/site-api/routes/public.ts +25 -0
- package/template/server/console/index.ts +26 -0
- package/template/server/console/runner.ts +67 -0
- package/template/server/console/tasks/example.ts +29 -0
- package/template/server/console/tasks/seed-admin.ts +71 -0
- package/template/server/core/context.ts +49 -0
- package/template/server/core/db.ts +12 -0
- package/template/server/core/email.ts +87 -0
- package/template/server/core/errors.ts +44 -0
- package/template/server/core/knexfile.ts +28 -0
- package/template/server/core/logger.ts +60 -0
- package/template/server/core/models/account.ts +319 -0
- package/template/server/core/models/auth.ts +317 -0
- package/template/server/core/models/base.ts +19 -0
- package/template/server/core/models/user.ts +343 -0
- package/template/server/core/s3.ts +183 -0
- package/template/server/emails/_styles.css +5 -0
- package/template/server/emails/_template.html +28 -0
- package/template/server/emails/accountWelcome.html +4 -0
- package/template/server/emails/forgetPassword.html +5 -0
- package/template/server/eslint.config.js +16 -0
- package/template/server/knex.sh +4 -0
- package/template/server/migrations/20260208000000_initial-schema.ts +56 -0
- package/template/server/migrations/20260208000001_seed-admin.ts +42 -0
- package/template/server/migrations/20260208000002_add-user-avatar.ts +13 -0
- package/template/server/package.json +58 -0
- package/template/server/tsconfig.json +27 -0
- package/template/server/types/api.ts +20 -0
- package/template/server/types/express.d.ts +35 -0
- package/template/ui/admin/.prettierrc +4 -0
- package/template/ui/admin/components.json +20 -0
- package/template/ui/admin/eslint.config.js +16 -0
- package/template/ui/admin/index.html +12 -0
- package/template/ui/admin/package.json +57 -0
- package/template/ui/admin/postcss.config.js +6 -0
- package/template/ui/admin/src/app.tsx +13 -0
- package/template/ui/admin/src/components/nav-user.tsx +95 -0
- package/template/ui/admin/src/components/theme-provider.tsx +65 -0
- package/template/ui/admin/src/components/theme-toggle.tsx +17 -0
- package/template/ui/admin/src/components/ui/badge.tsx +36 -0
- package/template/ui/admin/src/components/ui/button.tsx +56 -0
- package/template/ui/admin/src/components/ui/calendar.tsx +68 -0
- package/template/ui/admin/src/components/ui/card.tsx +79 -0
- package/template/ui/admin/src/components/ui/checkbox.tsx +28 -0
- package/template/ui/admin/src/components/ui/date-picker.tsx +78 -0
- package/template/ui/admin/src/components/ui/date-range-picker.tsx +208 -0
- package/template/ui/admin/src/components/ui/dialog.tsx +121 -0
- package/template/ui/admin/src/components/ui/dropdown-menu.tsx +200 -0
- package/template/ui/admin/src/components/ui/input.tsx +22 -0
- package/template/ui/admin/src/components/ui/label.tsx +24 -0
- package/template/ui/admin/src/components/ui/popover.tsx +29 -0
- package/template/ui/admin/src/components/ui/select.tsx +158 -0
- package/template/ui/admin/src/components/ui/separator.tsx +29 -0
- package/template/ui/admin/src/components/ui/sheet.tsx +136 -0
- package/template/ui/admin/src/components/ui/sidebar.tsx +759 -0
- package/template/ui/admin/src/components/ui/skeleton.tsx +15 -0
- package/template/ui/admin/src/components/ui/sonner.tsx +26 -0
- package/template/ui/admin/src/components/ui/switch.tsx +27 -0
- package/template/ui/admin/src/components/ui/table.tsx +117 -0
- package/template/ui/admin/src/components/ui/tabs.tsx +53 -0
- package/template/ui/admin/src/components/ui/textarea.tsx +22 -0
- package/template/ui/admin/src/components/ui/tooltip.tsx +28 -0
- package/template/ui/admin/src/hooks/queries/use-accounts.ts +68 -0
- package/template/ui/admin/src/hooks/queries/use-auth.ts +61 -0
- package/template/ui/admin/src/hooks/queries/use-dashboard.ts +70 -0
- package/template/ui/admin/src/hooks/queries/use-members.ts +90 -0
- package/template/ui/admin/src/hooks/queries/use-users.ts +99 -0
- package/template/ui/admin/src/hooks/use-confirm.tsx +92 -0
- package/template/ui/admin/src/hooks/use-mobile.tsx +22 -0
- package/template/ui/admin/src/hooks/use-sidebar.tsx +4 -0
- package/template/ui/admin/src/hooks/use-user.tsx +86 -0
- package/template/ui/admin/src/index.css +22 -0
- package/template/ui/admin/src/layouts/account.tsx +278 -0
- package/template/ui/admin/src/layouts/admin.tsx +182 -0
- package/template/ui/admin/src/layouts/public.tsx +9 -0
- package/template/ui/admin/src/layouts/user.tsx +107 -0
- package/template/ui/admin/src/lib/config.ts +9 -0
- package/template/ui/admin/src/lib/fetch-api.ts +101 -0
- package/template/ui/admin/src/lib/query-client.ts +12 -0
- package/template/ui/admin/src/lib/query-keys.ts +44 -0
- package/template/ui/admin/src/lib/utils.ts +24 -0
- package/template/ui/admin/src/main.tsx +17 -0
- package/template/ui/admin/src/pages/account/dashboard.tsx +128 -0
- package/template/ui/admin/src/pages/account/members.tsx +414 -0
- package/template/ui/admin/src/pages/account/settings.tsx +210 -0
- package/template/ui/admin/src/pages/admin/accounts.tsx +164 -0
- package/template/ui/admin/src/pages/admin/dashboard.tsx +243 -0
- package/template/ui/admin/src/pages/admin/users.tsx +395 -0
- package/template/ui/admin/src/pages/public/error.tsx +24 -0
- package/template/ui/admin/src/pages/public/forgot-password.tsx +74 -0
- package/template/ui/admin/src/pages/public/login.tsx +102 -0
- package/template/ui/admin/src/pages/public/reset-password.tsx +106 -0
- package/template/ui/admin/src/pages/public/signup.tsx +118 -0
- package/template/ui/admin/src/pages/user/profile.tsx +313 -0
- package/template/ui/admin/src/pages/user/select-account.tsx +114 -0
- package/template/ui/admin/src/router.tsx +71 -0
- package/template/ui/admin/tailwind.config.ts +71 -0
- package/template/ui/admin/tsconfig.json +24 -0
- package/template/ui/admin/vite.config.ts +15 -0
- package/template/ui/site/.env.sample +3 -0
- package/template/ui/site/.prettierrc +4 -0
- package/template/ui/site/app.ts +50 -0
- package/template/ui/site/eslint.config.js +16 -0
- package/template/ui/site/package.json +33 -0
- package/template/ui/site/styles/input.css +3 -0
- package/template/ui/site/tailwind.config.js +8 -0
- package/template/ui/site/tsconfig.json +15 -0
- package/template/ui/site/views/pages/home.ejs +305 -0
- package/template/ui/site/views/partials/footer.ejs +44 -0
- package/template/ui/site/views/partials/header.ejs +83 -0
- 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.
|