fix: batch 4 — connection pooling, graceful shutdown, email verification UX
- Prisma: connection_limit=10, pool_timeout=30 on DATABASE_URL in both compose files - Graceful shutdown: SIGTERM/SIGINT forwarded to Node process in docker-entrypoint.sh - testEmailConnection: replaced real email send with transporter.verify(), simplified UI to single button - NotificationLog.userId index: confirmed already present, no change needed Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { z } from 'zod'
|
||||
@@ -18,15 +17,6 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/components/ui/dialog'
|
||||
|
||||
const formSchema = z.object({
|
||||
smtp_host: z.string().min(1, 'SMTP host is required'),
|
||||
@@ -51,8 +41,6 @@ interface EmailSettingsFormProps {
|
||||
}
|
||||
|
||||
export function EmailSettingsForm({ settings }: EmailSettingsFormProps) {
|
||||
const [testDialogOpen, setTestDialogOpen] = useState(false)
|
||||
const [testEmail, setTestEmail] = useState('')
|
||||
const utils = trpc.useUtils()
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
@@ -77,17 +65,16 @@ export function EmailSettingsForm({ settings }: EmailSettingsFormProps) {
|
||||
},
|
||||
})
|
||||
|
||||
const sendTestEmail = trpc.settings.testEmailConnection.useMutation({
|
||||
const verifyConnection = trpc.settings.testEmailConnection.useMutation({
|
||||
onSuccess: (result) => {
|
||||
setTestDialogOpen(false)
|
||||
if (result.success) {
|
||||
toast.success('Test email sent successfully')
|
||||
toast.success('SMTP connection verified successfully')
|
||||
} else {
|
||||
toast.error(`Failed to send test email: ${result.error}`)
|
||||
toast.error(`SMTP verification failed: ${result.error}`)
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(`Test failed: ${error.message}`)
|
||||
toast.error(`Verification failed: ${error.message}`)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -107,12 +94,8 @@ export function EmailSettingsForm({ settings }: EmailSettingsFormProps) {
|
||||
updateSettings.mutate({ settings: settingsToUpdate })
|
||||
}
|
||||
|
||||
const handleSendTest = () => {
|
||||
if (!testEmail) {
|
||||
toast.error('Please enter an email address')
|
||||
return
|
||||
}
|
||||
sendTestEmail.mutate({ testEmail })
|
||||
const handleVerifyConnection = () => {
|
||||
verifyConnection.mutate()
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -243,49 +226,24 @@ export function EmailSettingsForm({ settings }: EmailSettingsFormProps) {
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Dialog open={testDialogOpen} onOpenChange={setTestDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button type="button" variant="outline">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleVerifyConnection}
|
||||
disabled={verifyConnection.isPending}
|
||||
>
|
||||
{verifyConnection.isPending ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Verifying...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Send className="mr-2 h-4 w-4" />
|
||||
Send Test Email
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Send Test Email</DialogTitle>
|
||||
<DialogDescription>
|
||||
Enter an email address to receive a test email
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="test@example.com"
|
||||
value={testEmail}
|
||||
onChange={(e) => setTestEmail(e.target.value)}
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setTestDialogOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSendTest}
|
||||
disabled={sendTestEmail.isPending}
|
||||
>
|
||||
{sendTestEmail.isPending ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Sending...
|
||||
</>
|
||||
) : (
|
||||
'Send Test'
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
Verify Connection
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -322,12 +322,11 @@ export const settingsRouter = router({
|
||||
* Test email connection
|
||||
*/
|
||||
testEmailConnection: superAdminProcedure
|
||||
.input(z.object({ testEmail: z.string().email() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
.mutation(async () => {
|
||||
try {
|
||||
const { sendTestEmail } = await import('@/lib/email')
|
||||
const success = await sendTestEmail(input.testEmail)
|
||||
return { success, error: success ? null : 'Failed to send test email' }
|
||||
const { verifyEmailConnection } = await import('@/lib/email')
|
||||
const success = await verifyEmailConnection()
|
||||
return { success, error: success ? null : 'SMTP connection verification failed' }
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
return { success: false, error: `Email configuration error: ${message}` }
|
||||
|
||||
Reference in New Issue
Block a user