fixes
Build And Push Image / docker (push) Successful in 3m54s
Details
Build And Push Image / docker (push) Successful in 3m54s
Details
This commit is contained in:
parent
4b1a77de90
commit
b833826a1e
|
|
@ -0,0 +1,102 @@
|
||||||
|
# Environment Variables Configuration
|
||||||
|
|
||||||
|
## NocoDB Configuration (Required)
|
||||||
|
|
||||||
|
To fix API key issues and improve container deployment, set these environment variables in your Docker container:
|
||||||
|
|
||||||
|
### Required Variables
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# NocoDB Database Connection
|
||||||
|
NUXT_NOCODB_URL=https://database.monacousa.org
|
||||||
|
NUXT_NOCODB_TOKEN=your_actual_nocodb_api_token_here
|
||||||
|
NUXT_NOCODB_BASE_ID=your_nocodb_base_id_here
|
||||||
|
```
|
||||||
|
|
||||||
|
### Alternative Variable Names (also supported)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Alternative formats that also work
|
||||||
|
NOCODB_URL=https://database.monacousa.org
|
||||||
|
NOCODB_TOKEN=your_actual_nocodb_api_token_here
|
||||||
|
NOCODB_API_TOKEN=your_actual_nocodb_api_token_here
|
||||||
|
NOCODB_BASE_ID=your_nocodb_base_id_here
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to Set in Docker
|
||||||
|
|
||||||
|
### Option 1: Docker Compose (Recommended)
|
||||||
|
|
||||||
|
Add to your `docker-compose.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
monacousa-portal:
|
||||||
|
image: your-image
|
||||||
|
environment:
|
||||||
|
- NUXT_NOCODB_URL=https://database.monacousa.org
|
||||||
|
- NUXT_NOCODB_TOKEN=your_actual_nocodb_api_token_here
|
||||||
|
- NUXT_NOCODB_BASE_ID=your_nocodb_base_id_here
|
||||||
|
# ... rest of your config
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Docker Run Command
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
-e NUXT_NOCODB_URL=https://database.monacousa.org \
|
||||||
|
-e NUXT_NOCODB_TOKEN=your_actual_nocodb_api_token_here \
|
||||||
|
-e NUXT_NOCODB_BASE_ID=your_nocodb_base_id_here \
|
||||||
|
your-image
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 3: Environment File
|
||||||
|
|
||||||
|
Create `.env` file:
|
||||||
|
```bash
|
||||||
|
NUXT_NOCODB_URL=https://database.monacousa.org
|
||||||
|
NUXT_NOCODB_TOKEN=your_actual_nocodb_api_token_here
|
||||||
|
NUXT_NOCODB_BASE_ID=your_nocodb_base_id_here
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use:
|
||||||
|
```bash
|
||||||
|
docker run --env-file .env your-image
|
||||||
|
```
|
||||||
|
|
||||||
|
## Priority Order
|
||||||
|
|
||||||
|
The system will check configuration in this order:
|
||||||
|
|
||||||
|
1. **Environment Variables** (highest priority)
|
||||||
|
2. Admin Panel Configuration (fallback)
|
||||||
|
3. Runtime Config (last resort)
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
✅ **Container-Friendly**: No need to configure through web UI
|
||||||
|
✅ **Secure**: API tokens stored as environment variables
|
||||||
|
✅ **Reliable**: No Unicode/formatting issues
|
||||||
|
✅ **Version Control**: Can be managed in deployment configs
|
||||||
|
✅ **Scalable**: Same config across multiple containers
|
||||||
|
|
||||||
|
## Getting Your Values
|
||||||
|
|
||||||
|
### NocoDB API Token
|
||||||
|
1. Go to your NocoDB instance
|
||||||
|
2. Click your profile → API Tokens
|
||||||
|
3. Create new token or copy existing one
|
||||||
|
4. Use the raw token without any formatting
|
||||||
|
|
||||||
|
### NocoDB Base ID
|
||||||
|
1. In NocoDB, go to your base
|
||||||
|
2. Check the URL: `https://your-nocodb.com/dashboard/#/nc/base/BASE_ID_HERE`
|
||||||
|
3. Copy the BASE_ID part
|
||||||
|
|
||||||
|
## Testing Configuration
|
||||||
|
|
||||||
|
After setting environment variables, check the logs:
|
||||||
|
- ✅ `[nocodb] ✅ Using environment variables - URL: https://database.monacousa.org`
|
||||||
|
- ✅ `[nocodb] ✅ Configuration validated successfully`
|
||||||
|
|
||||||
|
If you see fallback messages, the environment variables aren't being read correctly.
|
||||||
|
|
@ -57,39 +57,75 @@
|
||||||
<v-form ref="nocodbFormRef" v-model="nocodbFormValid">
|
<v-form ref="nocodbFormRef" v-model="nocodbFormValid">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-text-field
|
<div class="d-flex align-center gap-2">
|
||||||
v-model="nocodbForm.url"
|
<v-text-field
|
||||||
label="NocoDB URL"
|
v-model="nocodbForm.url"
|
||||||
variant="outlined"
|
label="NocoDB URL"
|
||||||
:rules="[rules.required, rules.url]"
|
variant="outlined"
|
||||||
required
|
:rules="[rules.required, rules.url]"
|
||||||
placeholder="https://database.monacousa.org"
|
:readonly="!editingFields.nocodbUrl"
|
||||||
/>
|
autocomplete="off"
|
||||||
|
required
|
||||||
|
placeholder="https://database.monacousa.org"
|
||||||
|
class="flex-grow-1"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
:icon="editingFields.nocodbUrl ? 'mdi-check' : 'mdi-pencil'"
|
||||||
|
:color="editingFields.nocodbUrl ? 'success' : 'primary'"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
@click="toggleEdit('nocodbUrl')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-text-field
|
<div class="d-flex align-center gap-2">
|
||||||
v-model="nocodbForm.apiKey"
|
<v-text-field
|
||||||
label="API Token"
|
v-model="nocodbForm.apiKey"
|
||||||
variant="outlined"
|
label="API Token"
|
||||||
:rules="[rules.required]"
|
variant="outlined"
|
||||||
required
|
:rules="[rules.required]"
|
||||||
:type="showNocodbApiKey ? 'text' : 'password'"
|
:readonly="!editingFields.nocodbApiKey"
|
||||||
:append-inner-icon="showNocodbApiKey ? 'mdi-eye' : 'mdi-eye-off'"
|
:type="showNocodbApiKey ? 'text' : 'password'"
|
||||||
@click:append-inner="showNocodbApiKey = !showNocodbApiKey"
|
:append-inner-icon="showNocodbApiKey ? 'mdi-eye' : 'mdi-eye-off'"
|
||||||
placeholder="Enter your NocoDB API token"
|
@click:append-inner="showNocodbApiKey = !showNocodbApiKey"
|
||||||
/>
|
autocomplete="off"
|
||||||
|
required
|
||||||
|
placeholder="Enter your NocoDB API token"
|
||||||
|
class="flex-grow-1"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
:icon="editingFields.nocodbApiKey ? 'mdi-check' : 'mdi-pencil'"
|
||||||
|
:color="editingFields.nocodbApiKey ? 'success' : 'primary'"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
@click="toggleEdit('nocodbApiKey')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-text-field
|
<div class="d-flex align-center gap-2">
|
||||||
v-model="nocodbForm.baseId"
|
<v-text-field
|
||||||
label="Base ID"
|
v-model="nocodbForm.baseId"
|
||||||
variant="outlined"
|
label="Base ID"
|
||||||
:rules="[rules.required]"
|
variant="outlined"
|
||||||
required
|
:rules="[rules.required]"
|
||||||
placeholder="your-base-id"
|
:readonly="!editingFields.nocodbBaseId"
|
||||||
/>
|
autocomplete="off"
|
||||||
|
required
|
||||||
|
placeholder="your-base-id"
|
||||||
|
class="flex-grow-1"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
:icon="editingFields.nocodbBaseId ? 'mdi-check' : 'mdi-pencil'"
|
||||||
|
:color="editingFields.nocodbBaseId ? 'success' : 'primary'"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
@click="toggleEdit('nocodbBaseId')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
|
|
@ -97,42 +133,78 @@
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-text-field
|
<div class="d-flex align-center gap-2">
|
||||||
v-model="nocodbForm.tables.members"
|
<v-text-field
|
||||||
label="Members Table ID"
|
v-model="nocodbForm.tables.members"
|
||||||
variant="outlined"
|
label="Members Table ID"
|
||||||
:rules="[rules.required]"
|
variant="outlined"
|
||||||
required
|
:rules="[rules.required]"
|
||||||
placeholder="members-table-id"
|
:readonly="!editingFields.membersTableId"
|
||||||
/>
|
autocomplete="off"
|
||||||
|
required
|
||||||
|
placeholder="members-table-id"
|
||||||
|
class="flex-grow-1"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
:icon="editingFields.membersTableId ? 'mdi-check' : 'mdi-pencil'"
|
||||||
|
:color="editingFields.membersTableId ? 'success' : 'primary'"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
@click="toggleEdit('membersTableId')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="text-caption text-medium-emphasis mt-1">
|
<div class="text-caption text-medium-emphasis mt-1">
|
||||||
Configure the table ID for the Members functionality
|
Configure the table ID for the Members functionality
|
||||||
</div>
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-text-field
|
<div class="d-flex align-center gap-2">
|
||||||
v-model="nocodbForm.tables.events"
|
<v-text-field
|
||||||
label="Events Table ID"
|
v-model="nocodbForm.tables.events"
|
||||||
variant="outlined"
|
label="Events Table ID"
|
||||||
:rules="[rules.required]"
|
variant="outlined"
|
||||||
required
|
:rules="[rules.required]"
|
||||||
placeholder="events-table-id"
|
:readonly="!editingFields.eventsTableId"
|
||||||
/>
|
autocomplete="off"
|
||||||
|
required
|
||||||
|
placeholder="events-table-id"
|
||||||
|
class="flex-grow-1"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
:icon="editingFields.eventsTableId ? 'mdi-check' : 'mdi-pencil'"
|
||||||
|
:color="editingFields.eventsTableId ? 'success' : 'primary'"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
@click="toggleEdit('eventsTableId')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="text-caption text-medium-emphasis mt-1">
|
<div class="text-caption text-medium-emphasis mt-1">
|
||||||
Configure the table ID for the Events functionality
|
Configure the table ID for the Events functionality
|
||||||
</div>
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-text-field
|
<div class="d-flex align-center gap-2">
|
||||||
v-model="nocodbForm.tables.rsvps"
|
<v-text-field
|
||||||
label="RSVPs Table ID"
|
v-model="nocodbForm.tables.rsvps"
|
||||||
variant="outlined"
|
label="RSVPs Table ID"
|
||||||
:rules="[rules.required]"
|
variant="outlined"
|
||||||
required
|
:rules="[rules.required]"
|
||||||
placeholder="rsvps-table-id"
|
:readonly="!editingFields.rsvpsTableId"
|
||||||
/>
|
autocomplete="off"
|
||||||
|
required
|
||||||
|
placeholder="rsvps-table-id"
|
||||||
|
class="flex-grow-1"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
:icon="editingFields.rsvpsTableId ? 'mdi-check' : 'mdi-pencil'"
|
||||||
|
:color="editingFields.rsvpsTableId ? 'success' : 'primary'"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
@click="toggleEdit('rsvpsTableId')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="text-caption text-medium-emphasis mt-1">
|
<div class="text-caption text-medium-emphasis mt-1">
|
||||||
Configure the table ID for the Event RSVPs functionality
|
Configure the table ID for the Event RSVPs functionality
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -186,32 +258,56 @@
|
||||||
<v-form ref="recaptchaFormRef" v-model="recaptchaFormValid">
|
<v-form ref="recaptchaFormRef" v-model="recaptchaFormValid">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-text-field
|
<div class="d-flex align-center gap-2">
|
||||||
v-model="recaptchaForm.siteKey"
|
<v-text-field
|
||||||
label="Site Key (Public)"
|
v-model="recaptchaForm.siteKey"
|
||||||
variant="outlined"
|
label="Site Key (Public)"
|
||||||
:rules="[rules.required]"
|
variant="outlined"
|
||||||
required
|
:rules="[rules.required]"
|
||||||
placeholder="6Lc..."
|
:readonly="!editingFields.recaptchaSiteKey"
|
||||||
hint="This key is visible to users on the frontend"
|
autocomplete="off"
|
||||||
persistent-hint
|
required
|
||||||
/>
|
placeholder="6Lc..."
|
||||||
|
hint="This key is visible to users on the frontend"
|
||||||
|
persistent-hint
|
||||||
|
class="flex-grow-1"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
:icon="editingFields.recaptchaSiteKey ? 'mdi-check' : 'mdi-pencil'"
|
||||||
|
:color="editingFields.recaptchaSiteKey ? 'success' : 'primary'"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
@click="toggleEdit('recaptchaSiteKey')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-text-field
|
<div class="d-flex align-center gap-2">
|
||||||
v-model="recaptchaForm.secretKey"
|
<v-text-field
|
||||||
label="Secret Key (Private)"
|
v-model="recaptchaForm.secretKey"
|
||||||
variant="outlined"
|
label="Secret Key (Private)"
|
||||||
:rules="[rules.required]"
|
variant="outlined"
|
||||||
required
|
:rules="[rules.required]"
|
||||||
:type="showRecaptchaSecret ? 'text' : 'password'"
|
:readonly="!editingFields.recaptchaSecretKey"
|
||||||
:append-inner-icon="showRecaptchaSecret ? 'mdi-eye' : 'mdi-eye-off'"
|
:type="showRecaptchaSecret ? 'text' : 'password'"
|
||||||
@click:append-inner="showRecaptchaSecret = !showRecaptchaSecret"
|
:append-inner-icon="showRecaptchaSecret ? 'mdi-eye' : 'mdi-eye-off'"
|
||||||
placeholder="6Lc..."
|
@click:append-inner="showRecaptchaSecret = !showRecaptchaSecret"
|
||||||
hint="This key is kept secret on the server"
|
autocomplete="off"
|
||||||
persistent-hint
|
required
|
||||||
/>
|
placeholder="6Lc..."
|
||||||
|
hint="This key is kept secret on the server"
|
||||||
|
persistent-hint
|
||||||
|
class="flex-grow-1"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
:icon="editingFields.recaptchaSecretKey ? 'mdi-check' : 'mdi-pencil'"
|
||||||
|
:color="editingFields.recaptchaSecretKey ? 'success' : 'primary'"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
@click="toggleEdit('recaptchaSecretKey')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
|
|
@ -249,17 +345,29 @@
|
||||||
<v-form ref="registrationFormRef" v-model="registrationFormValid">
|
<v-form ref="registrationFormRef" v-model="registrationFormValid">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<v-text-field
|
<div class="d-flex align-center gap-2">
|
||||||
v-model.number="registrationForm.membershipFee"
|
<v-text-field
|
||||||
label="Annual Membership Fee (EUR)"
|
v-model.number="registrationForm.membershipFee"
|
||||||
variant="outlined"
|
label="Annual Membership Fee (EUR)"
|
||||||
:rules="[rules.required, rules.positiveNumber]"
|
variant="outlined"
|
||||||
required
|
:rules="[rules.required, rules.positiveNumber]"
|
||||||
type="number"
|
:readonly="!editingFields.membershipFee"
|
||||||
min="1"
|
autocomplete="off"
|
||||||
placeholder="50"
|
required
|
||||||
prefix="€"
|
type="number"
|
||||||
/>
|
min="1"
|
||||||
|
placeholder="50"
|
||||||
|
prefix="€"
|
||||||
|
class="flex-grow-1"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
:icon="editingFields.membershipFee ? 'mdi-check' : 'mdi-pencil'"
|
||||||
|
:color="editingFields.membershipFee ? 'success' : 'primary'"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
@click="toggleEdit('membershipFee')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
|
|
@ -267,29 +375,53 @@
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-text-field
|
<div class="d-flex align-center gap-2">
|
||||||
v-model="registrationForm.iban"
|
<v-text-field
|
||||||
label="Bank IBAN"
|
v-model="registrationForm.iban"
|
||||||
variant="outlined"
|
label="Bank IBAN"
|
||||||
:rules="[rules.required, rules.iban]"
|
variant="outlined"
|
||||||
required
|
:rules="[rules.required, rules.iban]"
|
||||||
placeholder="DE89 3704 0044 0532 0130 00"
|
:readonly="!editingFields.iban"
|
||||||
hint="International Bank Account Number for membership dues"
|
autocomplete="off"
|
||||||
persistent-hint
|
required
|
||||||
/>
|
placeholder="DE89 3704 0044 0532 0130 00"
|
||||||
|
hint="International Bank Account Number for membership dues"
|
||||||
|
persistent-hint
|
||||||
|
class="flex-grow-1"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
:icon="editingFields.iban ? 'mdi-check' : 'mdi-pencil'"
|
||||||
|
:color="editingFields.iban ? 'success' : 'primary'"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
@click="toggleEdit('iban')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-text-field
|
<div class="d-flex align-center gap-2">
|
||||||
v-model="registrationForm.accountHolder"
|
<v-text-field
|
||||||
label="Account Holder Name"
|
v-model="registrationForm.accountHolder"
|
||||||
variant="outlined"
|
label="Account Holder Name"
|
||||||
:rules="[rules.required]"
|
variant="outlined"
|
||||||
required
|
:rules="[rules.required]"
|
||||||
placeholder="MonacoUSA Association"
|
:readonly="!editingFields.accountHolder"
|
||||||
hint="Name on the bank account"
|
autocomplete="off"
|
||||||
persistent-hint
|
required
|
||||||
/>
|
placeholder="MonacoUSA Association"
|
||||||
|
hint="Name on the bank account"
|
||||||
|
persistent-hint
|
||||||
|
class="flex-grow-1"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
:icon="editingFields.accountHolder ? 'mdi-check' : 'mdi-pencil'"
|
||||||
|
:color="editingFields.accountHolder ? 'success' : 'primary'"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
@click="toggleEdit('accountHolder')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
|
|
@ -335,32 +467,56 @@
|
||||||
<v-form ref="emailFormRef" v-model="emailFormValid">
|
<v-form ref="emailFormRef" v-model="emailFormValid">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<v-text-field
|
<div class="d-flex align-center gap-2">
|
||||||
v-model="emailForm.host"
|
<v-text-field
|
||||||
label="SMTP Host"
|
v-model="emailForm.host"
|
||||||
variant="outlined"
|
label="SMTP Host"
|
||||||
:rules="[rules.required]"
|
variant="outlined"
|
||||||
required
|
:rules="[rules.required]"
|
||||||
placeholder="smtp.gmail.com"
|
:readonly="!editingFields.smtpHost"
|
||||||
hint="Your SMTP server hostname"
|
autocomplete="off"
|
||||||
persistent-hint
|
required
|
||||||
/>
|
placeholder="smtp.gmail.com"
|
||||||
|
hint="Your SMTP server hostname"
|
||||||
|
persistent-hint
|
||||||
|
class="flex-grow-1"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
:icon="editingFields.smtpHost ? 'mdi-check' : 'mdi-pencil'"
|
||||||
|
:color="editingFields.smtpHost ? 'success' : 'primary'"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
@click="toggleEdit('smtpHost')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<v-text-field
|
<div class="d-flex align-center gap-2">
|
||||||
v-model.number="emailForm.port"
|
<v-text-field
|
||||||
label="Port"
|
v-model.number="emailForm.port"
|
||||||
variant="outlined"
|
label="Port"
|
||||||
:rules="[rules.required, rules.validPort]"
|
variant="outlined"
|
||||||
required
|
:rules="[rules.required, rules.validPort]"
|
||||||
type="number"
|
:readonly="!editingFields.smtpPort"
|
||||||
min="1"
|
autocomplete="off"
|
||||||
max="65535"
|
required
|
||||||
placeholder="587"
|
type="number"
|
||||||
hint="Usually 587 (TLS) or 465 (SSL)"
|
min="1"
|
||||||
persistent-hint
|
max="65535"
|
||||||
/>
|
placeholder="587"
|
||||||
|
hint="Usually 587 (TLS) or 465 (SSL)"
|
||||||
|
persistent-hint
|
||||||
|
class="flex-grow-1"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
:icon="editingFields.smtpPort ? 'mdi-check' : 'mdi-pencil'"
|
||||||
|
:color="editingFields.smtpPort ? 'success' : 'primary'"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
@click="toggleEdit('smtpPort')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
|
|
@ -377,55 +533,103 @@
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<v-text-field
|
<div class="d-flex align-center gap-2">
|
||||||
v-model="emailForm.username"
|
<v-text-field
|
||||||
label="Username"
|
v-model="emailForm.username"
|
||||||
variant="outlined"
|
label="Username"
|
||||||
placeholder="your-email@domain.com"
|
variant="outlined"
|
||||||
hint="SMTP authentication username (usually your email)"
|
:readonly="!editingFields.smtpUsername"
|
||||||
persistent-hint
|
autocomplete="off"
|
||||||
/>
|
placeholder="your-email@domain.com"
|
||||||
|
hint="SMTP authentication username (usually your email)"
|
||||||
|
persistent-hint
|
||||||
|
class="flex-grow-1"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
:icon="editingFields.smtpUsername ? 'mdi-check' : 'mdi-pencil'"
|
||||||
|
:color="editingFields.smtpUsername ? 'success' : 'primary'"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
@click="toggleEdit('smtpUsername')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<v-text-field
|
<div class="d-flex align-center gap-2">
|
||||||
v-model="emailForm.password"
|
<v-text-field
|
||||||
label="Password"
|
v-model="emailForm.password"
|
||||||
variant="outlined"
|
label="Password"
|
||||||
:type="showEmailPassword ? 'text' : 'password'"
|
variant="outlined"
|
||||||
:append-inner-icon="showEmailPassword ? 'mdi-eye' : 'mdi-eye-off'"
|
:readonly="!editingFields.smtpPassword"
|
||||||
@click:append-inner="showEmailPassword = !showEmailPassword"
|
:type="showEmailPassword ? 'text' : 'password'"
|
||||||
placeholder="Enter SMTP password"
|
:append-inner-icon="showEmailPassword ? 'mdi-eye' : 'mdi-eye-off'"
|
||||||
hint="SMTP authentication password or app password"
|
@click:append-inner="showEmailPassword = !showEmailPassword"
|
||||||
persistent-hint
|
autocomplete="off"
|
||||||
/>
|
placeholder="Enter SMTP password"
|
||||||
|
hint="SMTP authentication password or app password"
|
||||||
|
persistent-hint
|
||||||
|
class="flex-grow-1"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
:icon="editingFields.smtpPassword ? 'mdi-check' : 'mdi-pencil'"
|
||||||
|
:color="editingFields.smtpPassword ? 'success' : 'primary'"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
@click="toggleEdit('smtpPassword')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<v-text-field
|
<div class="d-flex align-center gap-2">
|
||||||
v-model="emailForm.fromAddress"
|
<v-text-field
|
||||||
label="From Email Address"
|
v-model="emailForm.fromAddress"
|
||||||
variant="outlined"
|
label="From Email Address"
|
||||||
:rules="[rules.required, rules.email]"
|
variant="outlined"
|
||||||
required
|
:rules="[rules.required, rules.email]"
|
||||||
type="email"
|
:readonly="!editingFields.smtpFromAddress"
|
||||||
placeholder="noreply@monacousa.org"
|
autocomplete="off"
|
||||||
hint="Email address that emails will be sent from"
|
required
|
||||||
persistent-hint
|
type="email"
|
||||||
/>
|
placeholder="noreply@monacousa.org"
|
||||||
|
hint="Email address that emails will be sent from"
|
||||||
|
persistent-hint
|
||||||
|
class="flex-grow-1"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
:icon="editingFields.smtpFromAddress ? 'mdi-check' : 'mdi-pencil'"
|
||||||
|
:color="editingFields.smtpFromAddress ? 'success' : 'primary'"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
@click="toggleEdit('smtpFromAddress')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<v-text-field
|
<div class="d-flex align-center gap-2">
|
||||||
v-model="emailForm.fromName"
|
<v-text-field
|
||||||
label="From Name"
|
v-model="emailForm.fromName"
|
||||||
variant="outlined"
|
label="From Name"
|
||||||
:rules="[rules.required]"
|
variant="outlined"
|
||||||
required
|
:rules="[rules.required]"
|
||||||
placeholder="MonacoUSA Portal"
|
:readonly="!editingFields.smtpFromName"
|
||||||
hint="Display name for outgoing emails"
|
autocomplete="off"
|
||||||
persistent-hint
|
required
|
||||||
/>
|
placeholder="MonacoUSA Portal"
|
||||||
|
hint="Display name for outgoing emails"
|
||||||
|
persistent-hint
|
||||||
|
class="flex-grow-1"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
:icon="editingFields.smtpFromName ? 'mdi-check' : 'mdi-pencil'"
|
||||||
|
:color="editingFields.smtpFromName ? 'success' : 'primary'"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
@click="toggleEdit('smtpFromName')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
|
|
@ -592,6 +796,32 @@ const emailTestStatus = ref<{ success: boolean; message: string } | null>(null);
|
||||||
// Test email address
|
// Test email address
|
||||||
const testEmailAddress = ref('');
|
const testEmailAddress = ref('');
|
||||||
|
|
||||||
|
// Editing state for fields (to prevent autofill interference)
|
||||||
|
const editingFields = ref({
|
||||||
|
nocodbUrl: false,
|
||||||
|
nocodbApiKey: false,
|
||||||
|
nocodbBaseId: false,
|
||||||
|
membersTableId: false,
|
||||||
|
eventsTableId: false,
|
||||||
|
rsvpsTableId: false,
|
||||||
|
recaptchaSiteKey: false,
|
||||||
|
recaptchaSecretKey: false,
|
||||||
|
membershipFee: false,
|
||||||
|
iban: false,
|
||||||
|
accountHolder: false,
|
||||||
|
smtpHost: false,
|
||||||
|
smtpPort: false,
|
||||||
|
smtpUsername: false,
|
||||||
|
smtpPassword: false,
|
||||||
|
smtpFromAddress: false,
|
||||||
|
smtpFromName: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle edit mode for a field
|
||||||
|
const toggleEdit = (fieldName: keyof typeof editingFields.value) => {
|
||||||
|
editingFields.value[fieldName] = !editingFields.value[fieldName];
|
||||||
|
};
|
||||||
|
|
||||||
// Form data
|
// Form data
|
||||||
const nocodbForm = ref<NocoDBSettings>({
|
const nocodbForm = ref<NocoDBSettings>({
|
||||||
url: 'https://database.monacousa.org',
|
url: 'https://database.monacousa.org',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
// server/api/admin/fix-event-attendee-counts.post.ts
|
||||||
|
import { createSessionManager } from '~/server/utils/session';
|
||||||
|
import { createNocoDBEventsClient } from '~/server/utils/nocodb-events';
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
try {
|
||||||
|
console.log('[admin/fix-event-attendee-counts] Starting attendee count fix...');
|
||||||
|
|
||||||
|
// Verify admin session
|
||||||
|
const sessionManager = createSessionManager();
|
||||||
|
const cookieHeader = getHeader(event, 'cookie');
|
||||||
|
const session = sessionManager.getSession(cookieHeader);
|
||||||
|
|
||||||
|
if (!session?.user || session.user.tier !== 'admin') {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 403,
|
||||||
|
statusMessage: 'Admin access required'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[admin/fix-event-attendee-counts] Authorized admin user:', session.user.email);
|
||||||
|
|
||||||
|
const eventsClient = createNocoDBEventsClient();
|
||||||
|
|
||||||
|
// Get all events
|
||||||
|
const eventsResponse = await eventsClient.findAll({ limit: 1000 });
|
||||||
|
const events = eventsResponse.list || [];
|
||||||
|
|
||||||
|
console.log('[admin/fix-event-attendee-counts] Found', events.length, 'events to process');
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
// Process each event
|
||||||
|
for (const eventObj of events) {
|
||||||
|
try {
|
||||||
|
const eventId = eventObj.event_id || eventObj.id || (eventObj as any).Id;
|
||||||
|
const eventTitle = eventObj.title || 'Unknown Event';
|
||||||
|
|
||||||
|
console.log('[admin/fix-event-attendee-counts] Processing event:', eventTitle, 'ID:', eventId);
|
||||||
|
|
||||||
|
// Force update the attendee count
|
||||||
|
const newCount = await eventsClient.forceUpdateEventAttendeeCount(eventId.toString());
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
event_id: eventId,
|
||||||
|
title: eventTitle,
|
||||||
|
old_count: eventObj.current_attendees || 0,
|
||||||
|
new_count: newCount,
|
||||||
|
status: 'success'
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('[admin/fix-event-attendee-counts] ✅ Fixed event:', eventTitle, 'Count:', newCount);
|
||||||
|
|
||||||
|
} catch (eventError: any) {
|
||||||
|
console.error('[admin/fix-event-attendee-counts] ❌ Error processing event:', eventObj.title, eventError);
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
event_id: eventObj.event_id || eventObj.id,
|
||||||
|
title: eventObj.title || 'Unknown Event',
|
||||||
|
old_count: eventObj.current_attendees || 0,
|
||||||
|
new_count: 0,
|
||||||
|
status: 'error',
|
||||||
|
error: eventError.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const successCount = results.filter(r => r.status === 'success').length;
|
||||||
|
const errorCount = results.filter(r => r.status === 'error').length;
|
||||||
|
|
||||||
|
console.log('[admin/fix-event-attendee-counts] ✅ Fix completed. Success:', successCount, 'Errors:', errorCount);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `Fixed attendee counts for ${successCount} events (${errorCount} errors)`,
|
||||||
|
data: {
|
||||||
|
total_events: events.length,
|
||||||
|
success_count: successCount,
|
||||||
|
error_count: errorCount,
|
||||||
|
results: results
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('[admin/fix-event-attendee-counts] ❌ Error:', error);
|
||||||
|
|
||||||
|
if (error.statusCode) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw createError({
|
||||||
|
statusCode: 500,
|
||||||
|
statusMessage: 'Failed to fix event attendee counts'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -135,12 +135,12 @@ export default defineEventHandler(async (event) => {
|
||||||
|
|
||||||
const newRSVP = await eventsClient.createRSVP(rsvpData);
|
const newRSVP = await eventsClient.createRSVP(rsvpData);
|
||||||
|
|
||||||
// Update event attendee count
|
// Update event attendee count using the new force update method
|
||||||
try {
|
try {
|
||||||
await updateEventAttendeeCount(eventId);
|
const newAttendeeCount = await eventsClient.forceUpdateEventAttendeeCount(eventId);
|
||||||
console.log('[RSVP] ✅ Updated event attendee count for event:', eventId);
|
console.log('[RSVP] ✅ Force updated event attendee count for event:', eventId, 'to:', newAttendeeCount);
|
||||||
} catch (countError) {
|
} catch (countError) {
|
||||||
console.log('[RSVP] ⚠️ Failed to update attendee count:', countError);
|
console.log('[RSVP] ⚠️ Failed to force update attendee count:', countError);
|
||||||
// Don't fail the RSVP creation if count update fails
|
// Don't fail the RSVP creation if count update fails
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -180,31 +180,9 @@ export const normalizeEventFieldsFromNocoDB = (data: any): Event => {
|
||||||
* Following the same pattern as the working members client
|
* Following the same pattern as the working members client
|
||||||
*/
|
*/
|
||||||
export function createNocoDBEventsClient() {
|
export function createNocoDBEventsClient() {
|
||||||
// Validate API token before using it (from incoming version)
|
// Use the centralized configuration from nocodb.ts which now prioritizes environment variables
|
||||||
const config = getNocoDbConfiguration();
|
const config = getNocoDbConfiguration();
|
||||||
const token = config.token;
|
console.log('[nocodb-events] ✅ Using NocoDB configuration (prioritizes environment variables)');
|
||||||
|
|
||||||
if (token) {
|
|
||||||
const cleanToken = token.trim();
|
|
||||||
|
|
||||||
// Check for non-ASCII characters that would cause ByteString errors
|
|
||||||
if (!/^[\x00-\xFF]*$/.test(cleanToken)) {
|
|
||||||
console.error('[nocodb-events] ❌ CRITICAL ERROR: API token contains invalid Unicode characters!');
|
|
||||||
throw createError({
|
|
||||||
statusCode: 500,
|
|
||||||
statusMessage: 'Events system: NocoDB API token contains invalid characters. Please reconfigure the database connection.'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Additional validation for common token issues
|
|
||||||
if (cleanToken.includes('•') || cleanToken.includes('…') || cleanToken.includes('"') || cleanToken.includes('"')) {
|
|
||||||
console.error('[nocodb-events] ❌ CRITICAL ERROR: API token contains formatting characters!');
|
|
||||||
throw createError({
|
|
||||||
statusCode: 500,
|
|
||||||
statusMessage: 'Events system: NocoDB API token contains formatting characters. Please reconfigure with the raw token from NocoDB.'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventsClient = {
|
const eventsClient = {
|
||||||
/**
|
/**
|
||||||
|
|
@ -737,6 +715,29 @@ export function createNocoDBEventsClient() {
|
||||||
console.error('[nocodb-events] ❌ Error calculating attendee count for event:', eventId, error);
|
console.error('[nocodb-events] ❌ Error calculating attendee count for event:', eventId, error);
|
||||||
return 0; // Return 0 if calculation fails
|
return 0; // Return 0 if calculation fails
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force update attendee count for an event and save to database
|
||||||
|
*/
|
||||||
|
async forceUpdateEventAttendeeCount(eventId: string): Promise<number> {
|
||||||
|
console.log('[nocodb-events] Force updating attendee count for event:', eventId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Calculate the current attendee count
|
||||||
|
const newCount = await this.calculateEventAttendeeCount(eventId);
|
||||||
|
|
||||||
|
// Update the event's current_attendees field directly
|
||||||
|
await this.update(eventId, {
|
||||||
|
current_attendees: newCount.toString()
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('[nocodb-events] ✅ Force updated event attendee count to:', newCount);
|
||||||
|
return newCount;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[nocodb-events] ❌ Error force updating attendee count:', error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -212,17 +212,29 @@ export const setGlobalNocoDBConfig = (config: any) => {
|
||||||
export const getNocoDbConfiguration = () => {
|
export const getNocoDbConfiguration = () => {
|
||||||
let configToUse: any = null;
|
let configToUse: any = null;
|
||||||
|
|
||||||
// Try to use the global configuration first
|
// 1. PRIORITY: Check for environment variables first (container-friendly)
|
||||||
if (globalNocoDBConfig) {
|
const envConfig = {
|
||||||
console.log('[nocodb] Using global configuration - URL:', globalNocoDBConfig.url);
|
url: process.env.NUXT_NOCODB_URL || process.env.NOCODB_URL,
|
||||||
|
token: process.env.NUXT_NOCODB_TOKEN || process.env.NOCODB_TOKEN || process.env.NOCODB_API_TOKEN,
|
||||||
|
baseId: process.env.NUXT_NOCODB_BASE_ID || process.env.NOCODB_BASE_ID
|
||||||
|
};
|
||||||
|
|
||||||
|
if (envConfig.url && envConfig.token) {
|
||||||
|
console.log('[nocodb] ✅ Using environment variables - URL:', envConfig.url);
|
||||||
|
configToUse = envConfig;
|
||||||
|
}
|
||||||
|
// 2. FALLBACK: Try to use the global admin panel configuration
|
||||||
|
else if (globalNocoDBConfig) {
|
||||||
|
console.log('[nocodb] Using admin panel configuration - URL:', globalNocoDBConfig.url);
|
||||||
configToUse = {
|
configToUse = {
|
||||||
url: globalNocoDBConfig.url,
|
url: globalNocoDBConfig.url,
|
||||||
token: globalNocoDBConfig.token,
|
token: globalNocoDBConfig.token,
|
||||||
baseId: globalNocoDBConfig.baseId
|
baseId: globalNocoDBConfig.baseId
|
||||||
};
|
};
|
||||||
} else {
|
}
|
||||||
// Fallback to runtime config
|
// 3. LAST RESORT: Runtime config
|
||||||
console.log('[nocodb] Global config not available, using runtime config');
|
else {
|
||||||
|
console.log('[nocodb] ⚠️ Using fallback runtime config');
|
||||||
const config = useRuntimeConfig().nocodb;
|
const config = useRuntimeConfig().nocodb;
|
||||||
configToUse = {
|
configToUse = {
|
||||||
...config,
|
...config,
|
||||||
|
|
@ -231,33 +243,42 @@ export const getNocoDbConfiguration = () => {
|
||||||
console.log('[nocodb] Fallback configuration URL:', configToUse.url);
|
console.log('[nocodb] Fallback configuration URL:', configToUse.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate API token before using it
|
// Validate configuration completeness
|
||||||
if (configToUse.token) {
|
if (!configToUse.url || !configToUse.token) {
|
||||||
const token = configToUse.token.trim();
|
console.error('[nocodb] ❌ CRITICAL ERROR: Incomplete NocoDB configuration!');
|
||||||
|
console.error('[nocodb] URL:', configToUse.url ? 'SET' : 'MISSING');
|
||||||
// Check for non-ASCII characters that would cause ByteString errors
|
console.error('[nocodb] Token:', configToUse.token ? 'SET' : 'MISSING');
|
||||||
if (!/^[\x00-\xFF]*$/.test(token)) {
|
console.error('[nocodb] Set environment variables: NUXT_NOCODB_URL, NUXT_NOCODB_TOKEN');
|
||||||
console.error('[nocodb] ❌ CRITICAL ERROR: API token contains invalid Unicode characters!');
|
throw createError({
|
||||||
console.error('[nocodb] This will cause ByteString conversion errors in HTTP headers.');
|
statusCode: 500,
|
||||||
console.error('[nocodb] Please update the API token in the admin configuration.');
|
statusMessage: 'NocoDB configuration incomplete. Set NUXT_NOCODB_URL and NUXT_NOCODB_TOKEN environment variables.'
|
||||||
throw createError({
|
});
|
||||||
statusCode: 500,
|
|
||||||
statusMessage: 'NocoDB API token contains invalid characters. Please reconfigure the database connection in the admin panel with a valid API token.'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Additional validation for common token issues
|
|
||||||
if (token.includes('•') || token.includes('…') || token.includes('"') || token.includes('"')) {
|
|
||||||
console.error('[nocodb] ❌ CRITICAL ERROR: API token contains formatting characters!');
|
|
||||||
console.error('[nocodb] Found characters like bullets (•), quotes, etc. that break HTTP headers.');
|
|
||||||
console.error('[nocodb] Please copy the raw API token from NocoDB without any formatting.');
|
|
||||||
throw createError({
|
|
||||||
statusCode: 500,
|
|
||||||
statusMessage: 'NocoDB API token contains formatting characters (bullets, quotes, etc.). Please reconfigure with the raw token from NocoDB.'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate API token before using it
|
||||||
|
const token = configToUse.token.trim();
|
||||||
|
|
||||||
|
// Check for non-ASCII characters that would cause ByteString errors
|
||||||
|
if (!/^[\x00-\xFF]*$/.test(token)) {
|
||||||
|
console.error('[nocodb] ❌ CRITICAL ERROR: API token contains invalid Unicode characters!');
|
||||||
|
console.error('[nocodb] This will cause ByteString conversion errors in HTTP headers.');
|
||||||
|
throw createError({
|
||||||
|
statusCode: 500,
|
||||||
|
statusMessage: 'NocoDB API token contains invalid characters. Please set a valid NUXT_NOCODB_TOKEN environment variable.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional validation for common token issues
|
||||||
|
if (token.includes('•') || token.includes('…') || token.includes('"') || token.includes('"')) {
|
||||||
|
console.error('[nocodb] ❌ CRITICAL ERROR: API token contains formatting characters!');
|
||||||
|
console.error('[nocodb] Found characters like bullets (•), quotes, etc. that break HTTP headers.');
|
||||||
|
throw createError({
|
||||||
|
statusCode: 500,
|
||||||
|
statusMessage: 'NocoDB API token contains formatting characters. Please set a clean NUXT_NOCODB_TOKEN environment variable.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[nocodb] ✅ Configuration validated successfully');
|
||||||
return configToUse;
|
return configToUse;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue