@rapidd/core 2.1.2 → 2.1.4
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/.env.example +9 -3
- package/README.md +26 -35
- package/bin/cli.js +26 -35
- package/config/app.json +1 -1
- package/dockerfile +9 -1
- package/locales/ar_SA.json +19 -1
- package/locales/de_DE.json +19 -1
- package/locales/en_US.json +19 -1
- package/locales/es_ES.json +19 -1
- package/locales/fr_FR.json +19 -1
- package/locales/it_IT.json +19 -1
- package/locales/ja_JP.json +19 -1
- package/locales/pt_BR.json +19 -1
- package/locales/ru_RU.json +19 -1
- package/locales/tr_TR.json +19 -1
- package/main.ts +5 -5
- package/package.json +1 -1
- package/src/app.ts +33 -6
- package/src/auth/Auth.ts +9 -8
- package/src/auth/stores/RedisStore.ts +6 -5
- package/src/auth/stores/index.ts +7 -6
- package/src/core/env.ts +7 -1
- package/src/core/i18n.ts +3 -2
- package/src/index.ts +3 -1
- package/src/orm/QueryBuilder.ts +5 -3
- package/src/plugins/auth.ts +21 -21
- package/src/plugins/language.ts +5 -5
- package/src/plugins/response.ts +4 -3
- package/src/plugins/upload.ts +8 -7
- package/src/types.ts +3 -3
- package/src/utils/Logger.ts +237 -0
- package/src/utils/index.ts +3 -0
package/.env.example
CHANGED
|
@@ -40,10 +40,10 @@ DB_USER_PASSWORD_FIELD=
|
|
|
40
40
|
# Auto-detected from unique string fields; comma-separated (default: email)
|
|
41
41
|
DB_USER_IDENTIFIER_FIELDS=
|
|
42
42
|
# Comma-separated: bearer, basic, cookie, header (default: bearer)
|
|
43
|
-
|
|
44
|
-
# Cookie name for cookie auth
|
|
43
|
+
AUTH_STRATEGIES=bearer
|
|
44
|
+
# Cookie name for cookie auth strategy (default: token)
|
|
45
45
|
AUTH_COOKIE_NAME=token
|
|
46
|
-
# Header name for header auth
|
|
46
|
+
# Header name for header auth strategy (default: X-Auth-Token)
|
|
47
47
|
AUTH_CUSTOM_HEADER=X-Auth-Token
|
|
48
48
|
|
|
49
49
|
# ── API Settings ───────────────────────────────────────
|
|
@@ -68,3 +68,9 @@ RLS_ENABLED=
|
|
|
68
68
|
# Namespace prefix for SQL session variables (default: app)
|
|
69
69
|
RLS_NAMESPACE=app
|
|
70
70
|
# Configure RLS variables in src/config/rls.ts
|
|
71
|
+
|
|
72
|
+
# ── Logging ───────────────────────────────────────────
|
|
73
|
+
# essential (default), fine, or finest
|
|
74
|
+
LOG_LEVEL=essential
|
|
75
|
+
# Directory for log files (default: logs/). Empty string disables file logging.
|
|
76
|
+
LOG_DIR=logs
|
package/README.md
CHANGED
|
@@ -31,18 +31,14 @@ Rapidd generates a fully-featured REST API from your database schema — then ge
|
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
mkdir my-api && cd my-api
|
|
34
|
-
npx rapidd create-project
|
|
35
|
-
npm install
|
|
34
|
+
npx @rapidd/core create-project && npm install
|
|
36
35
|
```
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb"
|
|
40
|
-
```
|
|
37
|
+
Set `DATABASE_URL` in `.env`, then add your schema in `prisma/schema.prisma`(or create from DB via `npx prisma db pull`):
|
|
41
38
|
|
|
42
39
|
```bash
|
|
43
|
-
npx
|
|
44
|
-
|
|
45
|
-
npm run dev # http://localhost:3000
|
|
40
|
+
npx rapidd build # generate models, routes & ACL scaffold
|
|
41
|
+
npm run dev # http://localhost:3000
|
|
46
42
|
```
|
|
47
43
|
|
|
48
44
|
Every table gets full CRUD endpoints. Auth is enabled automatically when a user table is detected. Every auto-detected value — auth fields, password hashing, JWT secrets, session store — is overridable via env vars. See [`.env.example`](.env.example) for the full list.
|
|
@@ -51,25 +47,22 @@ Every table gets full CRUD endpoints. Auth is enabled automatically when a user
|
|
|
51
47
|
|
|
52
48
|
---
|
|
53
49
|
|
|
54
|
-
##
|
|
55
|
-
|
|
56
|
-
|
|
|
57
|
-
|
|
58
|
-
|
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
| Per-model ACL | ✓ | ✓ |
|
|
64
|
-
| Row-
|
|
65
|
-
|
|
|
66
|
-
|
|
|
67
|
-
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
| Security headers (HSTS, CSP, etc.) | ✓ | ✓ |
|
|
71
|
-
|
|
72
|
-
> **MySQL note:** ACL provides application-level access control for all databases. RLS adds database-enforced row filtering as a second layer (PostgreSQL-only). For MySQL, ACL is your primary access control mechanism and covers most use cases. See the **[Access Control wiki](https://github.com/MertDalbudak/rapidd/wiki/Access-Control-(ACL))** for details.
|
|
50
|
+
## How It Compares
|
|
51
|
+
|
|
52
|
+
| | Rapidd | Hasura | PostgREST | Supabase | Strapi |
|
|
53
|
+
|---|:---:|:---:|:---:|:---:|:---:|
|
|
54
|
+
| Full source code ownership | ✓ | — | — | — | ✓ |
|
|
55
|
+
| Schema-first (no UI) | ✓ | ✓ | ✓ | ✓ | — |
|
|
56
|
+
| REST API | ✓ | partial | ✓ | ✓ | ✓ |
|
|
57
|
+
| Multi-database | ✓ | ✓ | — | — | ✓ |
|
|
58
|
+
| Built-in auth | ✓ | — | — | ✓ | ✓ |
|
|
59
|
+
| Per-model ACL | ✓ | ✓ | — | — | ✓ |
|
|
60
|
+
| Row-level security | ✓* | ✓ | ✓ | ✓ | — |
|
|
61
|
+
| Before/after middleware | ✓ | — | — | — | ✓ |
|
|
62
|
+
| Custom routes alongside generated | ✓ | — | — | ✓ | ✓ |
|
|
63
|
+
| No vendor dependency | ✓ | ✓ | ✓ | — | ✓ |
|
|
64
|
+
|
|
65
|
+
<sub>* PostgreSQL only. All other features support PostgreSQL and MySQL/MariaDB.</sub>
|
|
73
66
|
|
|
74
67
|
---
|
|
75
68
|
|
|
@@ -101,24 +94,24 @@ POST /auth/refresh { "refreshToken": "..." }
|
|
|
101
94
|
GET /auth/me Authorization: Bearer <token>
|
|
102
95
|
```
|
|
103
96
|
|
|
104
|
-
Four
|
|
97
|
+
Four strategies — **bearer** (default), **basic**, **cookie**, and **custom header** — configurable globally via `AUTH_STRATEGIES` env var or per endpoint prefix in `config/app.json`:
|
|
105
98
|
|
|
106
99
|
```json
|
|
107
100
|
{
|
|
108
|
-
"
|
|
101
|
+
"endpointAuthStrategy": {
|
|
109
102
|
"/api/v1": ["basic", "bearer"],
|
|
110
103
|
"/api/v2": "bearer"
|
|
111
104
|
}
|
|
112
105
|
}
|
|
113
106
|
```
|
|
114
107
|
|
|
115
|
-
Set `null` for the global default, a string for a single
|
|
108
|
+
Set `null` for the global default, a string for a single strategy, or an array for multiple. Route-level config takes highest priority, then prefix match, then global default.
|
|
116
109
|
|
|
117
110
|
Multi-identifier login lets users authenticate with any unique field (email, username, phone) in a single endpoint.
|
|
118
111
|
|
|
119
112
|
**Production:** `JWT_SECRET` and `JWT_REFRESH_SECRET` must be set explicitly. The server refuses to start without them to prevent session invalidation on restart.
|
|
120
113
|
|
|
121
|
-
> **[Authentication wiki](https://github.com/MertDalbudak/rapidd/wiki/Authentication)** — session stores, route protection, per-endpoint
|
|
114
|
+
> **[Authentication wiki](https://github.com/MertDalbudak/rapidd/wiki/Authentication)** — session stores, route protection, per-endpoint strategy overrides
|
|
122
115
|
|
|
123
116
|
---
|
|
124
117
|
|
|
@@ -235,11 +228,9 @@ docker build -t my-api . && docker run -p 3000:3000 --env-file .env my-api
|
|
|
235
228
|
| [`@rapidd/core`](https://www.npmjs.com/package/@rapidd/core) | Framework runtime, project scaffolding, and unified `npx rapidd` CLI |
|
|
236
229
|
| [`@rapidd/build`](https://www.npmjs.com/package/@rapidd/build) | Code generation — models, routes, and ACL from your Prisma schema |
|
|
237
230
|
|
|
238
|
-
All commands go through `npx rapidd`:
|
|
239
|
-
|
|
240
231
|
```bash
|
|
241
|
-
npx rapidd create-project # scaffold a new project
|
|
242
|
-
npx rapidd build
|
|
232
|
+
npx @rapidd/core create-project # scaffold a new project
|
|
233
|
+
npx rapidd build # generate from schema (after npm install)
|
|
243
234
|
```
|
|
244
235
|
|
|
245
236
|
---
|
package/bin/cli.js
CHANGED
|
@@ -87,6 +87,19 @@ function createProject() {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
// Read versions from this package's own package.json so they never drift
|
|
91
|
+
const corePkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf-8'));
|
|
92
|
+
const coreDeps = corePkg.dependencies || {};
|
|
93
|
+
const coreDevDeps = corePkg.devDependencies || {};
|
|
94
|
+
|
|
95
|
+
function pick(source, keys) {
|
|
96
|
+
const result = {};
|
|
97
|
+
for (const key of keys) {
|
|
98
|
+
if (source[key]) result[key] = source[key];
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
90
103
|
// Generate a fresh package.json for the new project
|
|
91
104
|
const pkg = {
|
|
92
105
|
name: projectName,
|
|
@@ -97,41 +110,19 @@ function createProject() {
|
|
|
97
110
|
dev: 'tsx watch main.ts',
|
|
98
111
|
build: 'tsc',
|
|
99
112
|
},
|
|
100
|
-
engines: { node: '>=24.0.0' },
|
|
101
|
-
dependencies:
|
|
102
|
-
'@fastify/cookie'
|
|
103
|
-
'@
|
|
104
|
-
'
|
|
105
|
-
'
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
'@
|
|
109
|
-
'@
|
|
110
|
-
'@
|
|
111
|
-
'
|
|
112
|
-
|
|
113
|
-
'ejs': '^4.0.1',
|
|
114
|
-
'fastify': '^5.2.1',
|
|
115
|
-
'fastify-plugin': '^5.0.1',
|
|
116
|
-
'ioredis': '^5.6.1',
|
|
117
|
-
'jsonwebtoken': '^9.0.2',
|
|
118
|
-
'luxon': '^3.7.2',
|
|
119
|
-
'nodemailer': '^8.0.1',
|
|
120
|
-
'pg': '^8.16.3',
|
|
121
|
-
},
|
|
122
|
-
devDependencies: {
|
|
123
|
-
'@rapidd/build': '^2.1.3',
|
|
124
|
-
'@types/bcrypt': '^6.0.0',
|
|
125
|
-
'@types/ejs': '^3.1.5',
|
|
126
|
-
'@types/jsonwebtoken': '^9.0.8',
|
|
127
|
-
'@types/luxon': '^3.7.1',
|
|
128
|
-
'@types/node': '^22.12.0',
|
|
129
|
-
'@types/nodemailer': '^7.0.9',
|
|
130
|
-
'@types/pg': '^8.11.11',
|
|
131
|
-
'prisma': '^7.0.2',
|
|
132
|
-
'tsx': '^4.19.2',
|
|
133
|
-
'typescript': '^5.7.3',
|
|
134
|
-
},
|
|
113
|
+
engines: corePkg.engines || { node: '>=24.0.0' },
|
|
114
|
+
dependencies: pick(coreDeps, [
|
|
115
|
+
'@fastify/cookie', '@fastify/cors', '@fastify/formbody', '@fastify/multipart', '@fastify/static',
|
|
116
|
+
'@prisma/adapter-mariadb', '@prisma/adapter-pg', '@prisma/client', '@prisma/internals',
|
|
117
|
+
'bcrypt', 'dotenv', 'ejs', 'fastify', 'fastify-plugin',
|
|
118
|
+
'ioredis', 'jsonwebtoken', 'luxon', 'nodemailer', 'pg',
|
|
119
|
+
]),
|
|
120
|
+
devDependencies: pick(coreDevDeps, [
|
|
121
|
+
'@rapidd/build',
|
|
122
|
+
'@types/bcrypt', '@types/ejs', '@types/jsonwebtoken', '@types/luxon',
|
|
123
|
+
'@types/node', '@types/nodemailer', '@types/pg',
|
|
124
|
+
'prisma', 'tsx', 'typescript',
|
|
125
|
+
]),
|
|
135
126
|
};
|
|
136
127
|
|
|
137
128
|
fs.writeFileSync(
|
package/config/app.json
CHANGED
package/dockerfile
CHANGED
|
@@ -25,7 +25,7 @@ COPY prisma ./prisma
|
|
|
25
25
|
RUN npx prisma generate --generator client
|
|
26
26
|
|
|
27
27
|
# Stage 3: Runtime
|
|
28
|
-
FROM node:
|
|
28
|
+
FROM node:current-alpine
|
|
29
29
|
|
|
30
30
|
WORKDIR /app
|
|
31
31
|
|
|
@@ -50,8 +50,16 @@ COPY --chown=rapidd:nodejs public ./public
|
|
|
50
50
|
|
|
51
51
|
RUN apk update && apk upgrade --no-cache && rm -rf /var/cache/apk/*
|
|
52
52
|
|
|
53
|
+
RUN mkdir -p /app/uploads /app/temp/uploads /app/logs && \
|
|
54
|
+
chown -R rapidd:nodejs /app/uploads /app/temp /app/logs
|
|
55
|
+
|
|
56
|
+
VOLUME ["/app/uploads", "/app/logs"]
|
|
57
|
+
|
|
53
58
|
USER rapidd
|
|
54
59
|
|
|
55
60
|
EXPOSE 3000
|
|
56
61
|
|
|
62
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
63
|
+
CMD node -e "fetch('http://localhost:3000/').then(r => process.exit(r.status === 404 ? 0 : 1)).catch(() => process.exit(1))"
|
|
64
|
+
|
|
57
65
|
ENTRYPOINT ["node", "dist/main.js"]
|
package/locales/ar_SA.json
CHANGED
|
@@ -175,5 +175,23 @@
|
|
|
175
175
|
"token_missing_email": "الرمز لا يحتوي على معلومات البريد الإلكتروني",
|
|
176
176
|
"oauth_config_missing": "يجب تكوين معرف التطبيق والسر الخاص بـ {provider} في متغيرات البيئة",
|
|
177
177
|
"token_app_mismatch": "الرمز لا ينتمي إلى هذا التطبيق",
|
|
178
|
-
"oauth_email_permission_missing": "مستخدم {provider} لم يمنح إذن البريد الإلكتروني"
|
|
178
|
+
"oauth_email_permission_missing": "مستخدم {provider} لم يمنح إذن البريد الإلكتروني",
|
|
179
|
+
"internal_server_error": "حدث خطأ ما",
|
|
180
|
+
"duplicate_entry": "إدخال مكرر لـ {model}. السجل ذو {field}: '{value}' موجود بالفعل",
|
|
181
|
+
"authentication_not_available": "المصادقة غير متاحة",
|
|
182
|
+
"authentication_required": "المصادقة مطلوبة",
|
|
183
|
+
"user_and_password_required": "المستخدم وكلمة المرور مطلوبان",
|
|
184
|
+
"auth_not_configured": "المصادقة غير مهيأة",
|
|
185
|
+
"email_and_password_required": "البريد الإلكتروني وكلمة المرور مطلوبان",
|
|
186
|
+
"email_already_exists": "البريد الإلكتروني موجود بالفعل",
|
|
187
|
+
"user_registered": "تم تسجيل المستخدم بنجاح",
|
|
188
|
+
"invalid_composite_key": "مفتاح مركب غير صالح: متوقع {expected} حقول، تم استلام {received}",
|
|
189
|
+
"invalid_composite_key_format": "تنسيق مفتاح مركب غير صالح. متوقع كائن أو سلسلة نصية مفصولة بعلامة التلدة",
|
|
190
|
+
"no_permission_to_create": "لا توجد صلاحية لإنشاء {model}",
|
|
191
|
+
"no_permission_to_update": "لا توجد صلاحية لتحديث السجل",
|
|
192
|
+
"no_permission_to_delete": "لا توجد صلاحية لحذف السجل",
|
|
193
|
+
"field_not_nullable": "الحقل '{field}' لا يمكن أن يكون فارغاً",
|
|
194
|
+
"relation_not_included": "العلاقة '{relation}' مُشار إليها في الحقول لكنها غير مُضمّنة. {hint}",
|
|
195
|
+
"max_nesting_depth_exceeded": "تم تجاوز أقصى عمق تداخل وهو {depth}",
|
|
196
|
+
"no_file_uploaded": "لم يتم رفع أي ملف"
|
|
179
197
|
}
|
package/locales/de_DE.json
CHANGED
|
@@ -175,5 +175,23 @@
|
|
|
175
175
|
"token_missing_email": "Token enthält keine E-Mail-Informationen",
|
|
176
176
|
"oauth_config_missing": "{provider} App-ID und Secret müssen in Umgebungsvariablen konfiguriert werden",
|
|
177
177
|
"token_app_mismatch": "Token gehört nicht zu dieser Anwendung",
|
|
178
|
-
"oauth_email_permission_missing": "{provider}-Benutzer hat keine E-Mail-Berechtigung erteilt"
|
|
178
|
+
"oauth_email_permission_missing": "{provider}-Benutzer hat keine E-Mail-Berechtigung erteilt",
|
|
179
|
+
"internal_server_error": "Etwas ist schiefgelaufen",
|
|
180
|
+
"duplicate_entry": "Doppelter Eintrag für {model}. Datensatz mit {field}: '{value}' existiert bereits",
|
|
181
|
+
"authentication_not_available": "Authentifizierung ist nicht verfügbar",
|
|
182
|
+
"authentication_required": "Authentifizierung erforderlich",
|
|
183
|
+
"user_and_password_required": "Benutzer und Passwort sind erforderlich",
|
|
184
|
+
"auth_not_configured": "Authentifizierung ist nicht konfiguriert",
|
|
185
|
+
"email_and_password_required": "E-Mail und Passwort sind erforderlich",
|
|
186
|
+
"email_already_exists": "E-Mail existiert bereits",
|
|
187
|
+
"user_registered": "Benutzer erfolgreich registriert",
|
|
188
|
+
"invalid_composite_key": "Ungültiger zusammengesetzter Schlüssel: {expected} Felder erwartet, {received} erhalten",
|
|
189
|
+
"invalid_composite_key_format": "Ungültiges Format für zusammengesetzten Schlüssel. Erwartet wird ein Objekt oder ein durch Tilde getrennter String",
|
|
190
|
+
"no_permission_to_create": "Keine Berechtigung zum Erstellen von {model}",
|
|
191
|
+
"no_permission_to_update": "Keine Berechtigung zum Aktualisieren des Datensatzes",
|
|
192
|
+
"no_permission_to_delete": "Keine Berechtigung zum Löschen des Datensatzes",
|
|
193
|
+
"field_not_nullable": "Feld '{field}' darf nicht null sein",
|
|
194
|
+
"relation_not_included": "Relation '{relation}' wird in Feldern referenziert, aber nicht eingeschlossen. {hint}",
|
|
195
|
+
"max_nesting_depth_exceeded": "Maximale Verschachtelungstiefe von {depth} überschritten",
|
|
196
|
+
"no_file_uploaded": "Keine Datei hochgeladen"
|
|
179
197
|
}
|
package/locales/en_US.json
CHANGED
|
@@ -176,5 +176,23 @@
|
|
|
176
176
|
"token_missing_email": "Token does not contain email information",
|
|
177
177
|
"oauth_config_missing": "{provider} App ID and Secret must be configured in environment variables",
|
|
178
178
|
"token_app_mismatch": "Token does not belong to this application",
|
|
179
|
-
"oauth_email_permission_missing": "{provider} user does not have email permission granted"
|
|
179
|
+
"oauth_email_permission_missing": "{provider} user does not have email permission granted",
|
|
180
|
+
"internal_server_error": "Something went wrong",
|
|
181
|
+
"duplicate_entry": "Duplicate entry for {model}. Record with {field}: '{value}' already exists",
|
|
182
|
+
"authentication_not_available": "Authentication is not available",
|
|
183
|
+
"authentication_required": "Authentication required",
|
|
184
|
+
"user_and_password_required": "User and password are required",
|
|
185
|
+
"auth_not_configured": "Authentication is not configured",
|
|
186
|
+
"email_and_password_required": "Email and password are required",
|
|
187
|
+
"email_already_exists": "Email already exists",
|
|
188
|
+
"user_registered": "User registered successfully",
|
|
189
|
+
"invalid_composite_key": "Invalid composite key: expected {expected} fields, received {received}",
|
|
190
|
+
"invalid_composite_key_format": "Invalid composite key format. Expected an object or tilde-separated string",
|
|
191
|
+
"no_permission_to_create": "No permission to create {model}",
|
|
192
|
+
"no_permission_to_update": "No permission to update record",
|
|
193
|
+
"no_permission_to_delete": "No permission to delete record",
|
|
194
|
+
"field_not_nullable": "Field '{field}' cannot be null",
|
|
195
|
+
"relation_not_included": "Relation '{relation}' is referenced in fields but not included. {hint}",
|
|
196
|
+
"max_nesting_depth_exceeded": "Maximum nesting depth of {depth} exceeded",
|
|
197
|
+
"no_file_uploaded": "No file uploaded"
|
|
180
198
|
}
|
package/locales/es_ES.json
CHANGED
|
@@ -175,5 +175,23 @@
|
|
|
175
175
|
"token_missing_email": "El token no contiene información de correo electrónico",
|
|
176
176
|
"oauth_config_missing": "El ID de aplicación y el secreto de {provider} deben configurarse en las variables de entorno",
|
|
177
177
|
"token_app_mismatch": "El token no pertenece a esta aplicación",
|
|
178
|
-
"oauth_email_permission_missing": "El usuario de {provider} no ha otorgado permiso de correo electrónico"
|
|
178
|
+
"oauth_email_permission_missing": "El usuario de {provider} no ha otorgado permiso de correo electrónico",
|
|
179
|
+
"internal_server_error": "Algo salió mal",
|
|
180
|
+
"duplicate_entry": "Entrada duplicada para {model}. El registro con {field}: '{value}' ya existe",
|
|
181
|
+
"authentication_not_available": "La autenticación no está disponible",
|
|
182
|
+
"authentication_required": "Autenticación requerida",
|
|
183
|
+
"user_and_password_required": "Usuario y contraseña son requeridos",
|
|
184
|
+
"auth_not_configured": "La autenticación no está configurada",
|
|
185
|
+
"email_and_password_required": "Correo electrónico y contraseña son requeridos",
|
|
186
|
+
"email_already_exists": "El correo electrónico ya existe",
|
|
187
|
+
"user_registered": "Usuario registrado exitosamente",
|
|
188
|
+
"invalid_composite_key": "Clave compuesta inválida: se esperaban {expected} campos, se recibieron {received}",
|
|
189
|
+
"invalid_composite_key_format": "Formato de clave compuesta inválido. Se espera un objeto o una cadena separada por tildes",
|
|
190
|
+
"no_permission_to_create": "Sin permiso para crear {model}",
|
|
191
|
+
"no_permission_to_update": "Sin permiso para actualizar el registro",
|
|
192
|
+
"no_permission_to_delete": "Sin permiso para eliminar el registro",
|
|
193
|
+
"field_not_nullable": "El campo '{field}' no puede ser nulo",
|
|
194
|
+
"relation_not_included": "La relación '{relation}' está referenciada en los campos pero no incluida. {hint}",
|
|
195
|
+
"max_nesting_depth_exceeded": "Se excedió la profundidad máxima de anidación de {depth}",
|
|
196
|
+
"no_file_uploaded": "No se cargó ningún archivo"
|
|
179
197
|
}
|
package/locales/fr_FR.json
CHANGED
|
@@ -175,5 +175,23 @@
|
|
|
175
175
|
"token_missing_email": "Le jeton ne contient pas d'informations d'e-mail",
|
|
176
176
|
"oauth_config_missing": "L'ID d'application et le secret de {provider} doivent être configurés dans les variables d'environnement",
|
|
177
177
|
"token_app_mismatch": "Le jeton n'appartient pas à cette application",
|
|
178
|
-
"oauth_email_permission_missing": "L'utilisateur {provider} n'a pas accordé la permission d'accès à l'e-mail"
|
|
178
|
+
"oauth_email_permission_missing": "L'utilisateur {provider} n'a pas accordé la permission d'accès à l'e-mail",
|
|
179
|
+
"internal_server_error": "Une erreur est survenue",
|
|
180
|
+
"duplicate_entry": "Entrée en double pour {model}. L'enregistrement avec {field} : '{value}' existe déjà",
|
|
181
|
+
"authentication_not_available": "L'authentification n'est pas disponible",
|
|
182
|
+
"authentication_required": "Authentification requise",
|
|
183
|
+
"user_and_password_required": "Utilisateur et mot de passe sont requis",
|
|
184
|
+
"auth_not_configured": "L'authentification n'est pas configurée",
|
|
185
|
+
"email_and_password_required": "E-mail et mot de passe sont requis",
|
|
186
|
+
"email_already_exists": "L'e-mail existe déjà",
|
|
187
|
+
"user_registered": "Utilisateur enregistré avec succès",
|
|
188
|
+
"invalid_composite_key": "Clé composite invalide : {expected} champs attendus, {received} reçus",
|
|
189
|
+
"invalid_composite_key_format": "Format de clé composite invalide. Un objet ou une chaîne séparée par des tildes est attendu",
|
|
190
|
+
"no_permission_to_create": "Pas de permission pour créer {model}",
|
|
191
|
+
"no_permission_to_update": "Pas de permission pour mettre à jour l'enregistrement",
|
|
192
|
+
"no_permission_to_delete": "Pas de permission pour supprimer l'enregistrement",
|
|
193
|
+
"field_not_nullable": "Le champ '{field}' ne peut pas être nul",
|
|
194
|
+
"relation_not_included": "La relation '{relation}' est référencée dans les champs mais non incluse. {hint}",
|
|
195
|
+
"max_nesting_depth_exceeded": "Profondeur d'imbrication maximale de {depth} dépassée",
|
|
196
|
+
"no_file_uploaded": "Aucun fichier téléchargé"
|
|
179
197
|
}
|
package/locales/it_IT.json
CHANGED
|
@@ -175,5 +175,23 @@
|
|
|
175
175
|
"token_missing_email": "Il token non contiene informazioni e-mail",
|
|
176
176
|
"oauth_config_missing": "L'ID applicazione e il segreto di {provider} devono essere configurati nelle variabili d'ambiente",
|
|
177
177
|
"token_app_mismatch": "Il token non appartiene a questa applicazione",
|
|
178
|
-
"oauth_email_permission_missing": "L'utente {provider} non ha concesso il permesso e-mail"
|
|
178
|
+
"oauth_email_permission_missing": "L'utente {provider} non ha concesso il permesso e-mail",
|
|
179
|
+
"internal_server_error": "Qualcosa è andato storto",
|
|
180
|
+
"duplicate_entry": "Voce duplicata per {model}. Il record con {field}: '{value}' esiste già",
|
|
181
|
+
"authentication_not_available": "L'autenticazione non è disponibile",
|
|
182
|
+
"authentication_required": "Autenticazione richiesta",
|
|
183
|
+
"user_and_password_required": "Utente e password sono obbligatori",
|
|
184
|
+
"auth_not_configured": "L'autenticazione non è configurata",
|
|
185
|
+
"email_and_password_required": "E-mail e password sono obbligatori",
|
|
186
|
+
"email_already_exists": "L'e-mail esiste già",
|
|
187
|
+
"user_registered": "Utente registrato con successo",
|
|
188
|
+
"invalid_composite_key": "Chiave composita non valida: previsti {expected} campi, ricevuti {received}",
|
|
189
|
+
"invalid_composite_key_format": "Formato chiave composita non valido. Previsto un oggetto o una stringa separata da tilde",
|
|
190
|
+
"no_permission_to_create": "Nessun permesso per creare {model}",
|
|
191
|
+
"no_permission_to_update": "Nessun permesso per aggiornare il record",
|
|
192
|
+
"no_permission_to_delete": "Nessun permesso per eliminare il record",
|
|
193
|
+
"field_not_nullable": "Il campo '{field}' non può essere nullo",
|
|
194
|
+
"relation_not_included": "La relazione '{relation}' è referenziata nei campi ma non inclusa. {hint}",
|
|
195
|
+
"max_nesting_depth_exceeded": "Profondità massima di annidamento di {depth} superata",
|
|
196
|
+
"no_file_uploaded": "Nessun file caricato"
|
|
179
197
|
}
|
package/locales/ja_JP.json
CHANGED
|
@@ -175,5 +175,23 @@
|
|
|
175
175
|
"token_missing_email": "トークンにメール情報が含まれていません",
|
|
176
176
|
"oauth_config_missing": "{provider}のアプリIDとシークレットを環境変数で設定する必要があります",
|
|
177
177
|
"token_app_mismatch": "トークンはこのアプリケーションに属していません",
|
|
178
|
-
"oauth_email_permission_missing": "{provider}ユーザーがメール許可を付与していません"
|
|
178
|
+
"oauth_email_permission_missing": "{provider}ユーザーがメール許可を付与していません",
|
|
179
|
+
"internal_server_error": "問題が発生しました",
|
|
180
|
+
"duplicate_entry": "{model}の重複エントリ。{field}: '{value}'のレコードは既に存在します",
|
|
181
|
+
"authentication_not_available": "認証は利用できません",
|
|
182
|
+
"authentication_required": "認証が必要です",
|
|
183
|
+
"user_and_password_required": "ユーザーとパスワードが必要です",
|
|
184
|
+
"auth_not_configured": "認証が設定されていません",
|
|
185
|
+
"email_and_password_required": "メールアドレスとパスワードが必要です",
|
|
186
|
+
"email_already_exists": "メールアドレスは既に存在します",
|
|
187
|
+
"user_registered": "ユーザーが正常に登録されました",
|
|
188
|
+
"invalid_composite_key": "無効な複合キー: {expected}フィールドが必要ですが、{received}を受信しました",
|
|
189
|
+
"invalid_composite_key_format": "無効な複合キー形式。オブジェクトまたはチルダ区切りの文字列が必要です",
|
|
190
|
+
"no_permission_to_create": "{model}を作成する権限がありません",
|
|
191
|
+
"no_permission_to_update": "レコードを更新する権限がありません",
|
|
192
|
+
"no_permission_to_delete": "レコードを削除する権限がありません",
|
|
193
|
+
"field_not_nullable": "フィールド'{field}'はnullにできません",
|
|
194
|
+
"relation_not_included": "リレーション'{relation}'はフィールドで参照されていますが、含まれていません。{hint}",
|
|
195
|
+
"max_nesting_depth_exceeded": "最大ネスト深度{depth}を超えました",
|
|
196
|
+
"no_file_uploaded": "ファイルがアップロードされていません"
|
|
179
197
|
}
|
package/locales/pt_BR.json
CHANGED
|
@@ -175,5 +175,23 @@
|
|
|
175
175
|
"token_missing_email": "O token não contém informações de e-mail",
|
|
176
176
|
"oauth_config_missing": "O ID do aplicativo e o segredo do {provider} devem ser configurados nas variáveis de ambiente",
|
|
177
177
|
"token_app_mismatch": "O token não pertence a esta aplicação",
|
|
178
|
-
"oauth_email_permission_missing": "O usuário do {provider} não concedeu permissão de e-mail"
|
|
178
|
+
"oauth_email_permission_missing": "O usuário do {provider} não concedeu permissão de e-mail",
|
|
179
|
+
"internal_server_error": "Algo deu errado",
|
|
180
|
+
"duplicate_entry": "Entrada duplicada para {model}. Registro com {field}: '{value}' já existe",
|
|
181
|
+
"authentication_not_available": "A autenticação não está disponível",
|
|
182
|
+
"authentication_required": "Autenticação necessária",
|
|
183
|
+
"user_and_password_required": "Usuário e senha são obrigatórios",
|
|
184
|
+
"auth_not_configured": "A autenticação não está configurada",
|
|
185
|
+
"email_and_password_required": "E-mail e senha são obrigatórios",
|
|
186
|
+
"email_already_exists": "E-mail já existe",
|
|
187
|
+
"user_registered": "Usuário registrado com sucesso",
|
|
188
|
+
"invalid_composite_key": "Chave composta inválida: esperados {expected} campos, recebidos {received}",
|
|
189
|
+
"invalid_composite_key_format": "Formato de chave composta inválido. Esperado um objeto ou string separada por til",
|
|
190
|
+
"no_permission_to_create": "Sem permissão para criar {model}",
|
|
191
|
+
"no_permission_to_update": "Sem permissão para atualizar o registro",
|
|
192
|
+
"no_permission_to_delete": "Sem permissão para excluir o registro",
|
|
193
|
+
"field_not_nullable": "O campo '{field}' não pode ser nulo",
|
|
194
|
+
"relation_not_included": "A relação '{relation}' é referenciada nos campos mas não está incluída. {hint}",
|
|
195
|
+
"max_nesting_depth_exceeded": "Profundidade máxima de aninhamento de {depth} excedida",
|
|
196
|
+
"no_file_uploaded": "Nenhum arquivo enviado"
|
|
179
197
|
}
|
package/locales/ru_RU.json
CHANGED
|
@@ -175,5 +175,23 @@
|
|
|
175
175
|
"token_missing_email": "Токен не содержит информацию об электронной почте",
|
|
176
176
|
"oauth_config_missing": "ID приложения и секрет {provider} должны быть настроены в переменных окружения",
|
|
177
177
|
"token_app_mismatch": "Токен не принадлежит этому приложению",
|
|
178
|
-
"oauth_email_permission_missing": "Пользователь {provider} не предоставил разрешение на доступ к электронной почте"
|
|
178
|
+
"oauth_email_permission_missing": "Пользователь {provider} не предоставил разрешение на доступ к электронной почте",
|
|
179
|
+
"internal_server_error": "Что-то пошло не так",
|
|
180
|
+
"duplicate_entry": "Дублирующая запись для {model}. Запись с {field}: '{value}' уже существует",
|
|
181
|
+
"authentication_not_available": "Аутентификация недоступна",
|
|
182
|
+
"authentication_required": "Требуется аутентификация",
|
|
183
|
+
"user_and_password_required": "Пользователь и пароль обязательны",
|
|
184
|
+
"auth_not_configured": "Аутентификация не настроена",
|
|
185
|
+
"email_and_password_required": "Электронная почта и пароль обязательны",
|
|
186
|
+
"email_already_exists": "Электронная почта уже существует",
|
|
187
|
+
"user_registered": "Пользователь успешно зарегистрирован",
|
|
188
|
+
"invalid_composite_key": "Недопустимый составной ключ: ожидалось {expected} полей, получено {received}",
|
|
189
|
+
"invalid_composite_key_format": "Недопустимый формат составного ключа. Ожидается объект или строка, разделенная тильдой",
|
|
190
|
+
"no_permission_to_create": "Нет разрешения на создание {model}",
|
|
191
|
+
"no_permission_to_update": "Нет разрешения на обновление записи",
|
|
192
|
+
"no_permission_to_delete": "Нет разрешения на удаление записи",
|
|
193
|
+
"field_not_nullable": "Поле '{field}' не может быть пустым",
|
|
194
|
+
"relation_not_included": "Связь '{relation}' указана в полях, но не включена. {hint}",
|
|
195
|
+
"max_nesting_depth_exceeded": "Превышена максимальная глубина вложенности {depth}",
|
|
196
|
+
"no_file_uploaded": "Файл не загружен"
|
|
179
197
|
}
|
package/locales/tr_TR.json
CHANGED
|
@@ -175,5 +175,23 @@
|
|
|
175
175
|
"token_missing_email": "Token e-posta bilgisi içermiyor",
|
|
176
176
|
"oauth_config_missing": "{provider} Uygulama ID'si ve Secret ortam değişkenlerinde yapılandırılmalıdır",
|
|
177
177
|
"token_app_mismatch": "Token bu uygulamaya ait değil",
|
|
178
|
-
"oauth_email_permission_missing": "{provider} kullanıcısı e-posta izni vermemiş"
|
|
178
|
+
"oauth_email_permission_missing": "{provider} kullanıcısı e-posta izni vermemiş",
|
|
179
|
+
"internal_server_error": "Bir şeyler ters gitti",
|
|
180
|
+
"duplicate_entry": "{model} için yinelenen kayıt. {field}: '{value}' değerine sahip kayıt zaten mevcut",
|
|
181
|
+
"authentication_not_available": "Kimlik doğrulama kullanılamıyor",
|
|
182
|
+
"authentication_required": "Kimlik doğrulama gerekli",
|
|
183
|
+
"user_and_password_required": "Kullanıcı ve şifre gerekli",
|
|
184
|
+
"auth_not_configured": "Kimlik doğrulama yapılandırılmamış",
|
|
185
|
+
"email_and_password_required": "E-posta ve şifre gerekli",
|
|
186
|
+
"email_already_exists": "E-posta zaten mevcut",
|
|
187
|
+
"user_registered": "Kullanıcı başarıyla kaydedildi",
|
|
188
|
+
"invalid_composite_key": "Geçersiz bileşik anahtar: {expected} alan bekleniyor, {received} alındı",
|
|
189
|
+
"invalid_composite_key_format": "Geçersiz bileşik anahtar formatı. Bir nesne veya tilde ile ayrılmış bir dize bekleniyor",
|
|
190
|
+
"no_permission_to_create": "{model} oluşturma izni yok",
|
|
191
|
+
"no_permission_to_update": "Kaydı güncelleme izni yok",
|
|
192
|
+
"no_permission_to_delete": "Kaydı silme izni yok",
|
|
193
|
+
"field_not_nullable": "'{field}' alanı null olamaz",
|
|
194
|
+
"relation_not_included": "'{relation}' ilişkisi alanlarda referans ediliyor ancak dahil edilmemiş. {hint}",
|
|
195
|
+
"max_nesting_depth_exceeded": "Maksimum iç içe geçme derinliği {depth} aşıldı",
|
|
196
|
+
"no_file_uploaded": "Dosya yüklenmedi"
|
|
179
197
|
}
|
package/main.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
2
|
import { getEnv } from './src/core/env';
|
|
3
3
|
import { buildApp } from './src/app';
|
|
4
|
+
import { Logger } from './src/utils/Logger';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Application entry point
|
|
@@ -14,16 +15,15 @@ export async function start(): Promise<void> {
|
|
|
14
15
|
const app = await buildApp();
|
|
15
16
|
|
|
16
17
|
await app.listen({ port, host });
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
Logger.log('Server running', { host, port });
|
|
19
|
+
Logger.log('Environment', { env: getEnv('NODE_ENV') });
|
|
19
20
|
|
|
20
21
|
// Warn if running compiled build with development NODE_ENV
|
|
21
22
|
if (process.argv[1]?.includes('/dist/') && getEnv('NODE_ENV') === 'development') {
|
|
22
|
-
|
|
23
|
-
console.warn('[Rapidd] Set NODE_ENV=production in your .env for production use.');
|
|
23
|
+
Logger.warn('Running compiled build with NODE_ENV=development. Set NODE_ENV=production in your .env for production use.');
|
|
24
24
|
}
|
|
25
25
|
} catch (err) {
|
|
26
|
-
|
|
26
|
+
Logger.error(err as Error);
|
|
27
27
|
process.exit(1);
|
|
28
28
|
}
|
|
29
29
|
}
|
package/package.json
CHANGED
package/src/app.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { LanguageDict } from './core/i18n';
|
|
|
12
12
|
import { disconnectAll } from './core/prisma';
|
|
13
13
|
import { validateEnv } from './core/env';
|
|
14
14
|
import { env } from './utils';
|
|
15
|
+
import { Logger } from './utils/Logger';
|
|
15
16
|
|
|
16
17
|
// Plugins
|
|
17
18
|
import securityPlugin from './plugins/security';
|
|
@@ -137,9 +138,35 @@ export async function buildApp(options: RapiddOptions = {}): Promise<FastifyInst
|
|
|
137
138
|
await loadRoutes(app, routesPath);
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
// ── Request Logging ─────────────────────────────
|
|
142
|
+
app.addHook('onSend', async (request, _reply, payload) => {
|
|
143
|
+
if (typeof payload === 'string') {
|
|
144
|
+
(request as any)._responsePayload = payload;
|
|
145
|
+
}
|
|
146
|
+
return payload;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
app.addHook('onResponse', async (request, reply) => {
|
|
150
|
+
Logger.request({
|
|
151
|
+
method: request.method,
|
|
152
|
+
url: request.url,
|
|
153
|
+
status: reply.statusCode,
|
|
154
|
+
time: reply.elapsedTime,
|
|
155
|
+
ip: request.ip,
|
|
156
|
+
contentLength: request.headers['content-length'],
|
|
157
|
+
userId: request.user?.id,
|
|
158
|
+
userAgent: request.headers['user-agent'],
|
|
159
|
+
requestHeaders: request.headers,
|
|
160
|
+
requestBody: request.body,
|
|
161
|
+
responseHeaders: reply.getHeaders(),
|
|
162
|
+
responseBody: (request as any)._responsePayload,
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
140
166
|
// ── 404 Handler ─────────────────────────────────
|
|
141
|
-
app.setNotFoundHandler((
|
|
142
|
-
|
|
167
|
+
app.setNotFoundHandler((request, reply) => {
|
|
168
|
+
const language = request.language || 'en_US';
|
|
169
|
+
reply.code(404).send({ status_code: 404, message: LanguageDict.get('record_not_found', null, language) });
|
|
143
170
|
});
|
|
144
171
|
|
|
145
172
|
// ── Graceful Shutdown ───────────────────────────
|
|
@@ -183,7 +210,7 @@ async function loadRoutes(app: FastifyInstance, routePath: string): Promise<void
|
|
|
183
210
|
await app.register(plugin, { prefix: route });
|
|
184
211
|
}
|
|
185
212
|
} catch (err) {
|
|
186
|
-
|
|
213
|
+
Logger.error(err as Error, { route });
|
|
187
214
|
}
|
|
188
215
|
}
|
|
189
216
|
}
|
|
@@ -193,12 +220,12 @@ async function loadRoutes(app: FastifyInstance, routePath: string): Promise<void
|
|
|
193
220
|
|
|
194
221
|
// Handle uncaught errors
|
|
195
222
|
process.on('uncaughtException', (err) => {
|
|
196
|
-
|
|
223
|
+
Logger.error(err);
|
|
197
224
|
process.exit(1);
|
|
198
225
|
});
|
|
199
226
|
|
|
200
|
-
process.on('unhandledRejection', (reason
|
|
201
|
-
|
|
227
|
+
process.on('unhandledRejection', (reason) => {
|
|
228
|
+
Logger.error(reason as Error);
|
|
202
229
|
});
|
|
203
230
|
|
|
204
231
|
export default buildApp;
|