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
package/template/code.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Code Standards
|
|
2
|
+
|
|
3
|
+
This document defines the coding standards for the project. All code
|
|
4
|
+
must follow these rules. Read `architecture.md` for architecture
|
|
5
|
+
patterns and how-tos.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Formatting
|
|
10
|
+
|
|
11
|
+
- **4-space indentation**, semicolons, LF line endings
|
|
12
|
+
- **No trailing commas** — not in arrays `[foo]`, objects `{foo}`,
|
|
13
|
+
function calls `(foo)`, or parameters. Never `(foo,)` or `[foo,]`
|
|
14
|
+
- **Max 80 columns** per line — especially for `if` conditions,
|
|
15
|
+
function signatures, and long strings. Break across lines:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// bad
|
|
19
|
+
if (userRole !== 'owner' && userRole !== 'admin' && userRole !== 'manager') {
|
|
20
|
+
|
|
21
|
+
// good
|
|
22
|
+
if (
|
|
23
|
+
userRole !== 'owner'
|
|
24
|
+
&& userRole !== 'admin'
|
|
25
|
+
&& userRole !== 'manager'
|
|
26
|
+
) {
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 2. Braces
|
|
30
|
+
|
|
31
|
+
**Always use braces** for `if`, `else`, `for`, `while`.
|
|
32
|
+
No single-line bodies without braces.
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// bad
|
|
36
|
+
if (foo) bar();
|
|
37
|
+
if (foo)
|
|
38
|
+
bar();
|
|
39
|
+
for (const x of arr) doSomething(x);
|
|
40
|
+
|
|
41
|
+
// good
|
|
42
|
+
if (foo) {
|
|
43
|
+
bar();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const x of arr) {
|
|
47
|
+
doSomething(x);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 3. Error Handling
|
|
52
|
+
|
|
53
|
+
**Never silently ignore errors.** Every `catch` block must at
|
|
54
|
+
minimum log the error with `logger.error()`:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// bad
|
|
58
|
+
try {
|
|
59
|
+
await deleteFromS3(key);
|
|
60
|
+
} catch {
|
|
61
|
+
// ignore
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// bad
|
|
65
|
+
try {
|
|
66
|
+
await deleteFromS3(key);
|
|
67
|
+
} catch (ex) { }
|
|
68
|
+
|
|
69
|
+
// good
|
|
70
|
+
try {
|
|
71
|
+
await deleteFromS3(key);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
logger.error('{context} failed to delete file', err);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
In route handlers, let errors propagate to `buildHandler()` — it
|
|
78
|
+
catches and formats them automatically. Only use try/catch in routes
|
|
79
|
+
when you need to handle a specific error differently.
|
|
80
|
+
|
|
81
|
+
## 4. TypeScript
|
|
82
|
+
|
|
83
|
+
- **TypeScript strict** in all packages
|
|
84
|
+
- Use explicit types for function parameters and return types on
|
|
85
|
+
exported functions
|
|
86
|
+
- Prefer `interface` over `type` for object shapes
|
|
87
|
+
- Use `unknown` over `any` when the type is truly unknown
|
|
88
|
+
- Use Zod schemas for runtime validation of external input
|
|
89
|
+
(request bodies, query params)
|
|
90
|
+
|
|
91
|
+
## 5. Language
|
|
92
|
+
|
|
93
|
+
- **Code in English** — variable names, function names, comments,
|
|
94
|
+
logs, error messages in code
|
|
95
|
+
- **UI text in Portuguese** — user-facing strings, labels, toast
|
|
96
|
+
messages, validation messages shown to users
|
|
97
|
+
- Error messages thrown by models should be in Portuguese (they're
|
|
98
|
+
sent to the frontend as-is)
|
|
99
|
+
|
|
100
|
+
## 6. Naming
|
|
101
|
+
|
|
102
|
+
| Type | Convention | Example |
|
|
103
|
+
|-------------|--------------|-------------------------------|
|
|
104
|
+
| Components | PascalCase | `NavUser.tsx` |
|
|
105
|
+
| Hooks | kebab-case | `use-user.tsx` |
|
|
106
|
+
| Query hooks | kebab-case | `use-accounts.ts` |
|
|
107
|
+
| Utils/Lib | kebab-case | `fetch-api.ts` |
|
|
108
|
+
| Routes | kebab-case | `demand-user.ts` |
|
|
109
|
+
| Models | kebab-case | `account.ts` |
|
|
110
|
+
| Migrations | timestamp | `20260208000000_description.ts`|
|
|
111
|
+
| DB columns | snake_case | `utc_created_on` |
|
|
112
|
+
| TS vars | camelCase | `accountRole` |
|
|
113
|
+
|
|
114
|
+
## 7. Path Aliases
|
|
115
|
+
|
|
116
|
+
- Server: `#core/*`, `#shared/*`, `#api/*`
|
|
117
|
+
- Admin UI: `@/*` (maps to `src/`)
|
|
118
|
+
|
|
119
|
+
Always use path aliases — never relative paths with `../../`.
|
|
120
|
+
|
|
121
|
+
## 8. Database
|
|
122
|
+
|
|
123
|
+
- **Raw SQL** in migrations (use `knex.schema.raw()`)
|
|
124
|
+
- Every table must have: `id` (UUID), `utc_created_on`, `utc_updated_on`
|
|
125
|
+
- Multi-tenant tables must have `account_id` with a foreign key
|
|
126
|
+
- Use `generate_series()` for date-based aggregations
|
|
127
|
+
- Filename format: `YYYYMMDDHHMMSS_description.ts`
|
|
128
|
+
- Never store sensitive data unencrypted (passwords use bcrypt)
|
|
129
|
+
|
|
130
|
+
## 9. Models
|
|
131
|
+
|
|
132
|
+
- Extend `BaseModel`, access DB via `this.knex`
|
|
133
|
+
- Access other models via `this.context.otherModel`
|
|
134
|
+
- Always filter by `account_id` in multi-tenant queries
|
|
135
|
+
- Use `PaginatedResult<T>`: `{ rows, total, page, limit, totalPages }`
|
|
136
|
+
- Use `whereILike` for case-insensitive search
|
|
137
|
+
- Parse JSON fields when reading, `JSON.stringify()` when writing
|
|
138
|
+
- Throw `AppError` subclasses: `ValidationError` (400),
|
|
139
|
+
`UnauthorizedError` (401), `ForbiddenError` (403),
|
|
140
|
+
`NotFoundError` (404), `ConflictError` (409)
|
|
141
|
+
|
|
142
|
+
## 10. Routes
|
|
143
|
+
|
|
144
|
+
- Use `buildHandler()` wrapper for all async handlers
|
|
145
|
+
- Export `makeEndpoint(app)` or `makeRoutes(app)`
|
|
146
|
+
- Use Zod schemas for all request body validation
|
|
147
|
+
- Middleware chain order:
|
|
148
|
+
`trySetUserByToken` → `demandUser` → `[demandAdmin|demandAccountAccess]`
|
|
149
|
+
- Never do raw DB queries in routes — use models
|
|
150
|
+
|
|
151
|
+
## 11. Frontend
|
|
152
|
+
|
|
153
|
+
- Use `fetchApi()` for all API calls (handles cookies, errors, FormData)
|
|
154
|
+
- Use React Query (`useQuery` / `useMutation`) for all server state
|
|
155
|
+
- Use `keepPreviousData` on paginated/searchable queries
|
|
156
|
+
- Add query keys to `lib/query-keys.ts`
|
|
157
|
+
- Invalidate related queries in mutation `onSuccess`
|
|
158
|
+
- Use `useConfirm()` instead of `window.confirm()`
|
|
159
|
+
- Use `toast` from sonner for feedback messages
|
|
160
|
+
|
|
161
|
+
## 12. File Uploads (S3)
|
|
162
|
+
|
|
163
|
+
- Store only the S3 **key** in the database (`avatar_key`,
|
|
164
|
+
`document_key`), never the full URL
|
|
165
|
+
- Serve files via **signed URLs** with short expiry (1 hour)
|
|
166
|
+
- Never expose raw S3 URLs to the frontend — always resolve to
|
|
167
|
+
signed URLs on the backend before sending
|
|
168
|
+
- Use `createS3Upload()` with explicit `allowedMimeTypes` and
|
|
169
|
+
`maxFileSize` — never accept all file types
|
|
170
|
+
- Delete old files from S3 when replacing (e.g. avatar update)
|
|
171
|
+
|
|
172
|
+
## 13. Charts
|
|
173
|
+
|
|
174
|
+
Use pure CSS/Tailwind bars with Radix Tooltip — no recharts library
|
|
175
|
+
needed (even though it's installed). Pattern:
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
<TooltipProvider delayDuration={0}>
|
|
179
|
+
<div className="flex items-end gap-1 h-32">
|
|
180
|
+
{data.map((point) => (
|
|
181
|
+
<Tooltip key={point.id}>
|
|
182
|
+
<TooltipTrigger asChild>
|
|
183
|
+
<div
|
|
184
|
+
className="flex-1 rounded-sm bg-primary
|
|
185
|
+
cursor-pointer transition-opacity
|
|
186
|
+
hover:opacity-80"
|
|
187
|
+
style={{ height: `${height}%` }}
|
|
188
|
+
/>
|
|
189
|
+
</TooltipTrigger>
|
|
190
|
+
<TooltipContent>
|
|
191
|
+
<p>{point.label}</p>
|
|
192
|
+
<p className="text-lg font-bold">{point.value}</p>
|
|
193
|
+
</TooltipContent>
|
|
194
|
+
</Tooltip>
|
|
195
|
+
))}
|
|
196
|
+
</div>
|
|
197
|
+
</TooltipProvider>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Calculate `maxValue` from data, derive bar heights as percentages
|
|
201
|
+
with a minimum of 4% so empty bars are still visible.
|
|
202
|
+
|
|
203
|
+
## 14. Security
|
|
204
|
+
|
|
205
|
+
- Auth via httpOnly cookies only — no tokens in localStorage
|
|
206
|
+
- All S3 files served through signed URLs (never public-read)
|
|
207
|
+
- Validate all input with Zod before processing
|
|
208
|
+
- Use parameterized queries (Knex handles this) — never
|
|
209
|
+
string-concatenate SQL
|
|
210
|
+
- Admin users bypass `user_in_accounts` but still go through
|
|
211
|
+
`demand-account-access` middleware which sets `req.accountRole`
|
package/template/dev.sh
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
echo "Starting All Services"
|
|
4
|
+
echo "====================="
|
|
5
|
+
echo ""
|
|
6
|
+
echo "Starting services:"
|
|
7
|
+
echo " - Admin API (port {{API_PORT}})"
|
|
8
|
+
echo " - Site API (port {{SITE_API_PORT}})"
|
|
9
|
+
echo " - Admin Panel (port {{ADMIN_UI_PORT}})"
|
|
10
|
+
echo " - Site (port {{SITE_PORT}})"
|
|
11
|
+
echo ""
|
|
12
|
+
echo "Press Ctrl+C to stop all services"
|
|
13
|
+
echo ""
|
|
14
|
+
|
|
15
|
+
trap 'kill $(jobs -p) 2>/dev/null' EXIT
|
|
16
|
+
|
|
17
|
+
if [ ! -f server/.env ]; then
|
|
18
|
+
echo "Warning: server/.env not found"
|
|
19
|
+
echo "Run ./install.sh first"
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
echo "Starting Admin API..."
|
|
24
|
+
(cd server && npm run dev) &
|
|
25
|
+
|
|
26
|
+
echo "Starting Site API..."
|
|
27
|
+
(cd server && npm run dev:site-api) &
|
|
28
|
+
|
|
29
|
+
echo "Starting Admin Panel..."
|
|
30
|
+
(cd ui/admin && npm run dev) &
|
|
31
|
+
|
|
32
|
+
echo "Starting Site..."
|
|
33
|
+
(cd ui/site && npm run dev) &
|
|
34
|
+
|
|
35
|
+
echo ""
|
|
36
|
+
echo "All services started!"
|
|
37
|
+
echo ""
|
|
38
|
+
echo "URLs:"
|
|
39
|
+
echo " - Site: http://localhost:{{SITE_PORT}}"
|
|
40
|
+
echo " - Admin Panel: http://localhost:{{ADMIN_UI_PORT}}"
|
|
41
|
+
echo " - Admin API: http://localhost:{{API_PORT}}"
|
|
42
|
+
echo " - Site API: http://localhost:{{SITE_API_PORT}}"
|
|
43
|
+
echo ""
|
|
44
|
+
|
|
45
|
+
wait
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
echo "Installing Dependencies"
|
|
4
|
+
echo "======================="
|
|
5
|
+
echo ""
|
|
6
|
+
|
|
7
|
+
echo "Installing server dependencies..."
|
|
8
|
+
(cd server && npm install)
|
|
9
|
+
echo "Server dependencies installed"
|
|
10
|
+
echo ""
|
|
11
|
+
|
|
12
|
+
echo "Installing admin UI dependencies..."
|
|
13
|
+
(cd ui/admin && npm install)
|
|
14
|
+
echo "Admin UI dependencies installed"
|
|
15
|
+
echo ""
|
|
16
|
+
|
|
17
|
+
echo "Installing site UI dependencies..."
|
|
18
|
+
(cd ui/site && npm install)
|
|
19
|
+
echo "Site UI dependencies installed"
|
|
20
|
+
echo ""
|
|
21
|
+
|
|
22
|
+
echo "Setting up environment files..."
|
|
23
|
+
echo ""
|
|
24
|
+
|
|
25
|
+
if [ ! -f server/.env ]; then
|
|
26
|
+
cp server/.env.sample server/.env
|
|
27
|
+
echo "Created server/.env"
|
|
28
|
+
else
|
|
29
|
+
echo "server/.env already exists"
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
if [ ! -f ui/site/.env ]; then
|
|
33
|
+
cp ui/site/.env.sample ui/site/.env
|
|
34
|
+
echo "Created ui/site/.env"
|
|
35
|
+
else
|
|
36
|
+
echo "ui/site/.env already exists"
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
echo ""
|
|
40
|
+
echo "======================="
|
|
41
|
+
echo "Installation Complete!"
|
|
42
|
+
echo ""
|
|
43
|
+
echo "Next steps:"
|
|
44
|
+
echo "1. Edit server/.env with your database credentials"
|
|
45
|
+
echo "2. ./seed.sh (create db + migrate + seed admin)"
|
|
46
|
+
echo "3. ./dev.sh (start all services)"
|
|
47
|
+
echo ""
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# {{PROJECT_NAME}} — Nginx Configuration
|
|
2
|
+
#
|
|
3
|
+
# Setup:
|
|
4
|
+
# 1. Copy this file to /etc/nginx/sites-available/{{PROJECT_SLUG}}
|
|
5
|
+
# 2. ln -s /etc/nginx/sites-available/{{PROJECT_SLUG}} /etc/nginx/sites-enabled/
|
|
6
|
+
# 3. Replace {{DOMAIN}} with your actual domain
|
|
7
|
+
# 4. Run: sudo certbot --nginx -d {{DOMAIN}} -d app.{{DOMAIN}} -d api.{{DOMAIN}}
|
|
8
|
+
# 5. sudo nginx -t && sudo systemctl reload nginx
|
|
9
|
+
|
|
10
|
+
# ─── Upstreams ───────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
upstream api {
|
|
13
|
+
ip_hash;
|
|
14
|
+
server 127.0.0.1:{{API_PORT}};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
upstream site_api {
|
|
18
|
+
ip_hash;
|
|
19
|
+
server 127.0.0.1:{{SITE_API_PORT}};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
upstream site {
|
|
23
|
+
ip_hash;
|
|
24
|
+
server 127.0.0.1:{{SITE_PORT}};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# ─── Admin API (api.{{DOMAIN}}) ──────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
server {
|
|
30
|
+
listen 443 ssl;
|
|
31
|
+
server_name api.{{DOMAIN}};
|
|
32
|
+
|
|
33
|
+
ssl_certificate /etc/letsencrypt/live/{{DOMAIN}}/fullchain.pem;
|
|
34
|
+
ssl_certificate_key /etc/letsencrypt/live/{{DOMAIN}}/privkey.pem;
|
|
35
|
+
include /etc/letsencrypt/options-ssl-nginx.conf;
|
|
36
|
+
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
|
37
|
+
|
|
38
|
+
client_max_body_size 50M;
|
|
39
|
+
|
|
40
|
+
location / {
|
|
41
|
+
proxy_pass http://api;
|
|
42
|
+
proxy_http_version 1.1;
|
|
43
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
44
|
+
proxy_set_header Connection "upgrade";
|
|
45
|
+
proxy_set_header Host $host;
|
|
46
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
47
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
48
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
49
|
+
proxy_read_timeout 86400s;
|
|
50
|
+
proxy_send_timeout 86400s;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# ─── Site API (api-site.{{DOMAIN}}) ──────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
server {
|
|
57
|
+
listen 443 ssl;
|
|
58
|
+
server_name api-site.{{DOMAIN}};
|
|
59
|
+
|
|
60
|
+
ssl_certificate /etc/letsencrypt/live/{{DOMAIN}}/fullchain.pem;
|
|
61
|
+
ssl_certificate_key /etc/letsencrypt/live/{{DOMAIN}}/privkey.pem;
|
|
62
|
+
include /etc/letsencrypt/options-ssl-nginx.conf;
|
|
63
|
+
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
|
64
|
+
|
|
65
|
+
client_max_body_size 50M;
|
|
66
|
+
|
|
67
|
+
location / {
|
|
68
|
+
proxy_pass http://site_api;
|
|
69
|
+
proxy_http_version 1.1;
|
|
70
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
71
|
+
proxy_set_header Connection "upgrade";
|
|
72
|
+
proxy_set_header Host $host;
|
|
73
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
74
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
75
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# ─── Admin Panel (app.{{DOMAIN}}) ────────────────────────────────────────────
|
|
80
|
+
# React SPA — static files served by nginx directly
|
|
81
|
+
|
|
82
|
+
server {
|
|
83
|
+
listen 443 ssl;
|
|
84
|
+
server_name app.{{DOMAIN}};
|
|
85
|
+
|
|
86
|
+
ssl_certificate /etc/letsencrypt/live/{{DOMAIN}}/fullchain.pem;
|
|
87
|
+
ssl_certificate_key /etc/letsencrypt/live/{{DOMAIN}}/privkey.pem;
|
|
88
|
+
include /etc/letsencrypt/options-ssl-nginx.conf;
|
|
89
|
+
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
|
90
|
+
|
|
91
|
+
root /apps/{{PROJECT_SLUG}}/builds/ui/admin/dist;
|
|
92
|
+
index index.html;
|
|
93
|
+
|
|
94
|
+
location / {
|
|
95
|
+
try_files $uri $uri/ /index.html;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Cache static assets
|
|
99
|
+
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
|
100
|
+
expires 1y;
|
|
101
|
+
add_header Cache-Control "public, immutable";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# ─── Public Site ({{DOMAIN}}) ────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
server {
|
|
108
|
+
listen 443 ssl;
|
|
109
|
+
server_name {{DOMAIN}};
|
|
110
|
+
|
|
111
|
+
ssl_certificate /etc/letsencrypt/live/{{DOMAIN}}/fullchain.pem;
|
|
112
|
+
ssl_certificate_key /etc/letsencrypt/live/{{DOMAIN}}/privkey.pem;
|
|
113
|
+
include /etc/letsencrypt/options-ssl-nginx.conf;
|
|
114
|
+
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
|
115
|
+
|
|
116
|
+
location / {
|
|
117
|
+
proxy_pass http://site;
|
|
118
|
+
proxy_http_version 1.1;
|
|
119
|
+
proxy_set_header Host $host;
|
|
120
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
121
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
122
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# ─── WWW Redirect ────────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
server {
|
|
129
|
+
listen 443 ssl;
|
|
130
|
+
server_name www.{{DOMAIN}};
|
|
131
|
+
|
|
132
|
+
ssl_certificate /etc/letsencrypt/live/{{DOMAIN}}/fullchain.pem;
|
|
133
|
+
ssl_certificate_key /etc/letsencrypt/live/{{DOMAIN}}/privkey.pem;
|
|
134
|
+
|
|
135
|
+
return 301 https://{{DOMAIN}}$request_uri;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# ─── HTTP → HTTPS Redirect ──────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
server {
|
|
141
|
+
listen 80;
|
|
142
|
+
server_name {{DOMAIN}} www.{{DOMAIN}} app.{{DOMAIN}} api.{{DOMAIN}} api-site.{{DOMAIN}};
|
|
143
|
+
|
|
144
|
+
location /.well-known/acme-challenge/ {
|
|
145
|
+
root /var/www/html;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
location / {
|
|
149
|
+
return 301 https://$host$request_uri;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# {{PROJECT_NAME}} — Admin API Service
|
|
2
|
+
#
|
|
3
|
+
# Setup:
|
|
4
|
+
# 1. Copy to /etc/systemd/system/{{PROJECT_SLUG}}-api.service
|
|
5
|
+
# 2. sudo systemctl daemon-reload
|
|
6
|
+
# 3. sudo systemctl enable {{PROJECT_SLUG}}-api
|
|
7
|
+
# 4. sudo systemctl start {{PROJECT_SLUG}}-api
|
|
8
|
+
#
|
|
9
|
+
# Logs:
|
|
10
|
+
# journalctl -u {{PROJECT_SLUG}}-api -f
|
|
11
|
+
# tail -f /apps/{{PROJECT_SLUG}}/log/api.log
|
|
12
|
+
|
|
13
|
+
[Unit]
|
|
14
|
+
Description={{PROJECT_NAME}} Admin API
|
|
15
|
+
After=network.target postgresql.service
|
|
16
|
+
Wants=postgresql.service
|
|
17
|
+
|
|
18
|
+
[Service]
|
|
19
|
+
Type=simple
|
|
20
|
+
User=ubuntu
|
|
21
|
+
Group=ubuntu
|
|
22
|
+
WorkingDirectory=/apps/{{PROJECT_SLUG}}/builds/server
|
|
23
|
+
ExecStart=/usr/bin/node apps/api/app.js
|
|
24
|
+
Restart=always
|
|
25
|
+
RestartSec=5
|
|
26
|
+
StandardOutput=append:/apps/{{PROJECT_SLUG}}/log/api.log
|
|
27
|
+
StandardError=append:/apps/{{PROJECT_SLUG}}/log/api.log
|
|
28
|
+
Environment=NODE_ENV=production
|
|
29
|
+
|
|
30
|
+
[Install]
|
|
31
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# {{PROJECT_NAME}} — Site API Service
|
|
2
|
+
#
|
|
3
|
+
# Setup:
|
|
4
|
+
# 1. Copy to /etc/systemd/system/{{PROJECT_SLUG}}-site-api.service
|
|
5
|
+
# 2. sudo systemctl daemon-reload
|
|
6
|
+
# 3. sudo systemctl enable {{PROJECT_SLUG}}-site-api
|
|
7
|
+
# 4. sudo systemctl start {{PROJECT_SLUG}}-site-api
|
|
8
|
+
#
|
|
9
|
+
# Logs:
|
|
10
|
+
# journalctl -u {{PROJECT_SLUG}}-site-api -f
|
|
11
|
+
# tail -f /apps/{{PROJECT_SLUG}}/log/site-api.log
|
|
12
|
+
|
|
13
|
+
[Unit]
|
|
14
|
+
Description={{PROJECT_NAME}} Site API
|
|
15
|
+
After=network.target postgresql.service
|
|
16
|
+
Wants=postgresql.service
|
|
17
|
+
|
|
18
|
+
[Service]
|
|
19
|
+
Type=simple
|
|
20
|
+
User=ubuntu
|
|
21
|
+
Group=ubuntu
|
|
22
|
+
WorkingDirectory=/apps/{{PROJECT_SLUG}}/builds/server
|
|
23
|
+
ExecStart=/usr/bin/node apps/site-api/app.js
|
|
24
|
+
Restart=always
|
|
25
|
+
RestartSec=5
|
|
26
|
+
StandardOutput=append:/apps/{{PROJECT_SLUG}}/log/site-api.log
|
|
27
|
+
StandardError=append:/apps/{{PROJECT_SLUG}}/log/site-api.log
|
|
28
|
+
Environment=NODE_ENV=production
|
|
29
|
+
|
|
30
|
+
[Install]
|
|
31
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# {{PROJECT_NAME}} — Public Site Service
|
|
2
|
+
#
|
|
3
|
+
# Setup:
|
|
4
|
+
# 1. Copy to /etc/systemd/system/{{PROJECT_SLUG}}-site.service
|
|
5
|
+
# 2. sudo systemctl daemon-reload
|
|
6
|
+
# 3. sudo systemctl enable {{PROJECT_SLUG}}-site
|
|
7
|
+
# 4. sudo systemctl start {{PROJECT_SLUG}}-site
|
|
8
|
+
#
|
|
9
|
+
# Logs:
|
|
10
|
+
# journalctl -u {{PROJECT_SLUG}}-site -f
|
|
11
|
+
# tail -f /apps/{{PROJECT_SLUG}}/log/site.log
|
|
12
|
+
|
|
13
|
+
[Unit]
|
|
14
|
+
Description={{PROJECT_NAME}} Public Site
|
|
15
|
+
After=network.target
|
|
16
|
+
|
|
17
|
+
[Service]
|
|
18
|
+
Type=simple
|
|
19
|
+
User=ubuntu
|
|
20
|
+
Group=ubuntu
|
|
21
|
+
WorkingDirectory=/apps/{{PROJECT_SLUG}}/builds/ui/site
|
|
22
|
+
ExecStart=/usr/bin/node app.js
|
|
23
|
+
Restart=always
|
|
24
|
+
RestartSec=5
|
|
25
|
+
StandardOutput=append:/apps/{{PROJECT_SLUG}}/log/site.log
|
|
26
|
+
StandardError=append:/apps/{{PROJECT_SLUG}}/log/site.log
|
|
27
|
+
Environment=NODE_ENV=production
|
|
28
|
+
|
|
29
|
+
[Install]
|
|
30
|
+
WantedBy=multi-user.target
|
package/template/seed.sh
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
echo "Database Setup"
|
|
4
|
+
echo "=============="
|
|
5
|
+
echo ""
|
|
6
|
+
|
|
7
|
+
DB_NAME="{{DB_NAME}}"
|
|
8
|
+
|
|
9
|
+
echo "Creating database '$DB_NAME'..."
|
|
10
|
+
createdb "$DB_NAME" 2>/dev/null && echo "Database created" || echo "Database already exists (or failed)"
|
|
11
|
+
echo ""
|
|
12
|
+
|
|
13
|
+
echo "Running migrations..."
|
|
14
|
+
(cd server && npm run migrate:latest)
|
|
15
|
+
echo ""
|
|
16
|
+
|
|
17
|
+
echo "Seeding admin user..."
|
|
18
|
+
(cd server && npm run console -- --task=seed-admin)
|
|
19
|
+
echo ""
|
|
20
|
+
|
|
21
|
+
echo "=============="
|
|
22
|
+
echo "Database ready!"
|
|
23
|
+
echo ""
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Server — Admin API
|
|
2
|
+
PORT={{API_PORT}}
|
|
3
|
+
NODE_ENV=development
|
|
4
|
+
|
|
5
|
+
# Server — Site API (optional second API)
|
|
6
|
+
SITE_API_PORT={{SITE_API_PORT}}
|
|
7
|
+
|
|
8
|
+
# URLs
|
|
9
|
+
DOMAIN=http://localhost:{{ADMIN_UI_PORT}}
|
|
10
|
+
SITE_DOMAIN=http://localhost:{{SITE_PORT}}
|
|
11
|
+
BASE_UI_URL=http://localhost:{{ADMIN_UI_PORT}}
|
|
12
|
+
BASE_API_URL=http://localhost:{{API_PORT}}
|
|
13
|
+
BASE_SITE_API_URL=http://localhost:{{SITE_API_PORT}}
|
|
14
|
+
|
|
15
|
+
# Database (PostgreSQL)
|
|
16
|
+
DB_HOST=localhost
|
|
17
|
+
DB_PORT=5432
|
|
18
|
+
DB_NAME={{DB_NAME}}
|
|
19
|
+
DB_USER=postgres
|
|
20
|
+
DB_PASSWORD=postgres
|
|
21
|
+
|
|
22
|
+
# Authentication
|
|
23
|
+
JWT_TOKEN_SECRET=your-secret-key-change-this-in-production
|
|
24
|
+
COOKIE_DOMAIN=
|
|
25
|
+
|
|
26
|
+
# Email
|
|
27
|
+
EMAIL_HOST=smtp.example.com
|
|
28
|
+
EMAIL_PORT=465
|
|
29
|
+
EMAIL_SECURE=true
|
|
30
|
+
EMAIL_USER=your-email@example.com
|
|
31
|
+
EMAIL_PASSWORD=your-email-password
|
|
32
|
+
EMAIL_FROM=noreply@{{DOMAIN}}
|
|
33
|
+
EMAIL_NAME={{PROJECT_NAME}}
|
|
34
|
+
EMAIL_REPLY_TO=suporte@{{DOMAIN}}
|
|
35
|
+
|
|
36
|
+
# AWS S3
|
|
37
|
+
AWS_REGION=us-east-1
|
|
38
|
+
AWS_ACCESS_KEY_ID=your-access-key
|
|
39
|
+
AWS_SECRET_ACCESS_KEY=your-secret-key
|
|
40
|
+
S3_BUCKET={{PROJECT_SLUG}}-files
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import dotenv from 'dotenv';
|
|
2
|
+
dotenv.config();
|
|
3
|
+
|
|
4
|
+
import express from 'express';
|
|
5
|
+
import cors from 'cors';
|
|
6
|
+
import cookieParser from 'cookie-parser';
|
|
7
|
+
import Context from '#core/context.js';
|
|
8
|
+
import logger from '#core/logger.js';
|
|
9
|
+
import makeRoutes from './routes/index.js';
|
|
10
|
+
import requestLoggerMiddleware from '#shared/middlewares/request-logger.js';
|
|
11
|
+
import errorHandler from '#shared/middlewares/error-handler.js';
|
|
12
|
+
|
|
13
|
+
const app = express();
|
|
14
|
+
const port = parseInt(process.env.PORT || '{{API_PORT}}', 10);
|
|
15
|
+
|
|
16
|
+
app.use(cors({
|
|
17
|
+
origin: process.env.DOMAIN || 'http://localhost:{{ADMIN_UI_PORT}}',
|
|
18
|
+
credentials: true
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
app.use(cookieParser());
|
|
22
|
+
app.use(express.json({ limit: '10mb' }));
|
|
23
|
+
app.use(express.urlencoded({ extended: true }));
|
|
24
|
+
app.use(requestLoggerMiddleware);
|
|
25
|
+
|
|
26
|
+
// Context injection — fresh Context per request
|
|
27
|
+
app.use((req: express.Request, _res: express.Response, next: express.NextFunction) => {
|
|
28
|
+
req.context = new Context();
|
|
29
|
+
next();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Mount all routes
|
|
33
|
+
makeRoutes(app);
|
|
34
|
+
|
|
35
|
+
// Error handler (must be last)
|
|
36
|
+
app.use(errorHandler);
|
|
37
|
+
|
|
38
|
+
app.listen(port, () => {
|
|
39
|
+
logger.info(`Admin API server running on port ${port}`);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export default app;
|