fix(admin): SMTP port can be cleared and saves without 'Ungültiges JSON'
The UI sent the port as a number while the server expects text, so every save failed; the field also jumped back to 587 when emptied. The port is now edited as text (digits only), and the server accepts number or text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
eaabe469d5
commit
a3a709440f
4 files changed
+56
-6
No files matched your search
@@ -96,7 +96,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
allowRegistration: false,
|
allowRegistration: false,
|
||||||
instanceName: 'MindSift',
|
instanceName: 'MindSift',
|
||||||
smtpHost: '',
|
smtpHost: '',
|
||||||
smtpPort: 587,
|
smtpPort: '587',
|
||||||
smtpUser: '',
|
smtpUser: '',
|
||||||
smtpPass: '',
|
smtpPass: '',
|
||||||
smtpFrom: '',
|
smtpFrom: '',
|
||||||
@@ -126,7 +126,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
allowRegistration: Boolean(sys.allowRegistration),
|
allowRegistration: Boolean(sys.allowRegistration),
|
||||||
instanceName: sys.instanceName || 'MindSift',
|
instanceName: sys.instanceName || 'MindSift',
|
||||||
smtpHost: sys.smtpHost || '',
|
smtpHost: sys.smtpHost || '',
|
||||||
smtpPort: sys.smtpPort || 587,
|
smtpPort: sys.smtpPort ? String(sys.smtpPort) : '587',
|
||||||
smtpUser: sys.smtpUser || '',
|
smtpUser: sys.smtpUser || '',
|
||||||
smtpPass: sys.smtpPass || '',
|
smtpPass: sys.smtpPass || '',
|
||||||
smtpFrom: sys.smtpFrom || '',
|
smtpFrom: sys.smtpFrom || '',
|
||||||
@@ -1150,10 +1150,12 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
SMTP Port (`SMTP_PORT`)
|
SMTP Port (`SMTP_PORT`)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="text"
|
||||||
value={settings.smtpPort || 587}
|
inputMode="numeric"
|
||||||
|
value={settings.smtpPort ?? ''}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setSettings((prev) => ({ ...prev, smtpPort: Number(e.target.value) }))
|
// Digits only; an empty field stays empty while typing (saved as 587)
|
||||||
|
setSettings((prev) => ({ ...prev, smtpPort: e.target.value.replace(/\D/g, '').slice(0, 5) }))
|
||||||
}
|
}
|
||||||
placeholder="587"
|
placeholder="587"
|
||||||
className="w-full h-9 px-3 input-field font-mono"
|
className="w-full h-9 px-3 input-field font-mono"
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ export interface SystemSettings {
|
|||||||
allowRegistration: boolean;
|
allowRegistration: boolean;
|
||||||
instanceName: string;
|
instanceName: string;
|
||||||
smtpHost?: string;
|
smtpHost?: string;
|
||||||
smtpPort?: number;
|
smtpPort?: string; // sent as text, like the server stores it
|
||||||
smtpUser?: string;
|
smtpUser?: string;
|
||||||
smtpPass?: string;
|
smtpPass?: string;
|
||||||
smtpFrom?: string;
|
smtpFrom?: string;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package domain
|
|||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -289,6 +290,36 @@ type SystemSettings struct {
|
|||||||
SMTPConfiguredViaEnv bool `json:"smtpConfiguredViaEnv,omitempty"`
|
SMTPConfiguredViaEnv bool `json:"smtpConfiguredViaEnv,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON accepts smtpPort as text or number: older clients sent a number, which made the whole
|
||||||
|
// settings request fail with "Ungültiges JSON".
|
||||||
|
func (s *SystemSettings) UnmarshalJSON(data []byte) error {
|
||||||
|
type plain SystemSettings
|
||||||
|
aux := struct {
|
||||||
|
*plain
|
||||||
|
SMTPPort json.RawMessage `json:"smtpPort"`
|
||||||
|
}{plain: (*plain)(s)}
|
||||||
|
if err := json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
port := strings.TrimSpace(string(aux.SMTPPort))
|
||||||
|
switch {
|
||||||
|
case port == "" || port == "null":
|
||||||
|
case strings.HasPrefix(port, `"`):
|
||||||
|
var str string
|
||||||
|
if err := json.Unmarshal(aux.SMTPPort, &str); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.SMTPPort = strings.TrimSpace(str)
|
||||||
|
default:
|
||||||
|
var n json.Number
|
||||||
|
if err := json.Unmarshal(aux.SMTPPort, &n); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.SMTPPort = n.String()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type DocumentBlock struct {
|
type DocumentBlock struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
DocumentID string `json:"documentId,omitempty"`
|
DocumentID string `json:"documentId,omitempty"`
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package domain
|
package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -32,3 +33,19 @@ func TestStaleAnchorTags(t *testing.T) {
|
|||||||
t.Fatalf("only tags that exist solely as anchors are stale, got %v", got)
|
t.Fatalf("only tags that exist solely as anchors are stale, got %v", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSystemSettingsAcceptsPortAsNumberOrText(t *testing.T) {
|
||||||
|
for _, body := range []string{`{"smtpPort": 465, "smtpHost": "mail"}`, `{"smtpPort": "465", "smtpHost": "mail"}`} {
|
||||||
|
var s SystemSettings
|
||||||
|
if err := json.Unmarshal([]byte(body), &s); err != nil {
|
||||||
|
t.Fatalf("%s: %v", body, err)
|
||||||
|
}
|
||||||
|
if s.SMTPPort != "465" || s.SMTPHost != "mail" {
|
||||||
|
t.Errorf("%s: got port %q host %q", body, s.SMTPPort, s.SMTPHost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var empty SystemSettings
|
||||||
|
if err := json.Unmarshal([]byte(`{"smtpPort": ""}`), &empty); err != nil || empty.SMTPPort != "" {
|
||||||
|
t.Errorf("empty port: %q (%v)", empty.SMTPPort, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user