From 2bf73e74e69b559b82536441d7bb41af99897676 Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Wed, 28 Aug 2024 09:44:12 -0500 Subject: [PATCH 1/5] Added timeout to email sending so it will show an error after 5 seconds --- server/funcs/email.go | 65 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/server/funcs/email.go b/server/funcs/email.go index 60941a7..f42abed 100644 --- a/server/funcs/email.go +++ b/server/funcs/email.go @@ -2,11 +2,14 @@ package funcs import ( "bytes" + "crypto/tls" "fmt" "html/template" + "net" "net/smtp" "server/config" "server/models" + "time" "github.com/sendgrid/sendgrid-go" "github.com/sendgrid/sendgrid-go/helpers/mail" @@ -23,7 +26,7 @@ func SendJudgeEmail(judge *models.Judge, hostname string) error { // If sendgrid API key exists, send email with sendgrid sendgridApiKey := config.GetOptEnv("SENDGRID_API_KEY", "") if sendgridApiKey != "" { - return SendgridEmail(sendgridApiKey, judge, hostname) + return sendgridEmail(sendgridApiKey, judge, hostname) } // Sender info @@ -59,7 +62,7 @@ func SendJudgeEmail(judge *models.Judge, hostname string) error { body.Write(html) // Send email! - err = smtp.SendMail(smtpHost+":"+smtpPort, auth, from, to, body.Bytes()) + err = sendEmailWithTimeout(smtpHost, smtpPort, auth, from, to, body.Bytes()) return err } @@ -88,7 +91,7 @@ func FillTemplate(name string, baseUrl string, code string, appName string) ([]b } // Send email with Sendgrid -func SendgridEmail(sendgridApiKey string, judge *models.Judge, hostname string) error { +func sendgridEmail(sendgridApiKey string, judge *models.Judge, hostname string) error { appName := config.GetEnv("VITE_JURY_NAME") from := mail.NewEmail(config.GetEnv("EMAIL_FROM_NAME"), config.GetEnv("EMAIL_FROM")) @@ -105,3 +108,59 @@ func SendgridEmail(sendgridApiKey string, judge *models.Judge, hostname string) _, err = client.Send(message) return err } + +func sendEmailWithTimeout(host string, port string, auth smtp.Auth, from string, to []string, body []byte) error { + // Dial SMTP with 5 second timeout + conn, err := net.DialTimeout("tcp", host+":"+port, 5*time.Second) + if err != nil { + return fmt.Errorf("failed to connect to email server: %v", err) + } + defer conn.Close() + + // Create SMTP client + client, err := smtp.NewClient(conn, host) + if err != nil { + return fmt.Errorf("failed to create SMTP client: %v", err) + } + defer client.Quit() + + // Initiate TLS connection + tlsConfig := &tls.Config{ + InsecureSkipVerify: true, // Set to true for self-signed certificates, but ideally use false and verify the server's certificate + ServerName: host, + } + if err := client.StartTLS(tlsConfig); err != nil { + return fmt.Errorf("failed to start email TLS client: %v", err) + } + + // Authenticate + if err := client.Auth(auth); err != nil { + return fmt.Errorf("failed to authenticate SMTP client: %v", err) + } + + // Set the sender and recipient + if err := client.Mail(from); err != nil { + return fmt.Errorf("failed to set email sender: %v", err) + } + for _, addr := range to { + if err := client.Rcpt(addr); err != nil { + return fmt.Errorf("failed to set email recipient: %v", err) + } + } + + // Send the email body + writer, err := client.Data() + if err != nil { + return fmt.Errorf("failed to get email writer: %v", err) + } + _, err = writer.Write(body) + if err != nil { + return fmt.Errorf("failed to write email body: %v", err) + } + err = writer.Close() + if err != nil { + return fmt.Errorf("failed to close email writer: %v", err) + } + + return nil +} From b2a4dfc84346b270277b9a3b1a4218257449217d Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Wed, 28 Aug 2024 09:44:39 -0500 Subject: [PATCH 2/5] Added loading popup when clicking 'add judge' or 'upload csv' to wait for email sending --- client/src/components/admin/add-judges/NewJudgeForm.tsx | 4 +++- client/src/components/admin/add-judges/UploadCSVForm.tsx | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/components/admin/add-judges/NewJudgeForm.tsx b/client/src/components/admin/add-judges/NewJudgeForm.tsx index 9f5f5a0..9c31fcb 100644 --- a/client/src/components/admin/add-judges/NewJudgeForm.tsx +++ b/client/src/components/admin/add-judges/NewJudgeForm.tsx @@ -5,6 +5,7 @@ import TextArea from '../../TextArea'; import { postRequest } from '../../../api'; import { errorAlert } from '../../../util'; import Checkbox from '../../Checkbox'; +import Loading from '../../Loading'; interface NewJudgeData { name: string; @@ -22,7 +23,7 @@ const NewJudgeForm = () => { const onSubmit: SubmitHandler = async (data) => { setIsSubmitting(true); - const newdata = { ...data, no_send: noSend } + const newdata: NewJudgeDataFull = { ...data, no_send: noSend }; const res = await postRequest('/judge/new', 'admin', newdata); if (res.status !== 200) { @@ -56,6 +57,7 @@ const NewJudgeForm = () => { + ); }; diff --git a/client/src/components/admin/add-judges/UploadCSVForm.tsx b/client/src/components/admin/add-judges/UploadCSVForm.tsx index e441b50..28c0744 100644 --- a/client/src/components/admin/add-judges/UploadCSVForm.tsx +++ b/client/src/components/admin/add-judges/UploadCSVForm.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { createHeaders } from '../../../api'; +import Loading from '../../Loading'; interface UploadCSVFormProps { /* The format of the CSV file */ @@ -180,6 +181,7 @@ const UploadCSVForm = (props: UploadCSVFormProps) => { + ); From b73ed461409920fd33c126f45fde6e9c15060d00 Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Wed, 28 Aug 2024 09:48:06 -0500 Subject: [PATCH 3/5] Added email port number by default to compose --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 89db668..bc7cf9b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: - MONGODB_URI=${MONGODB_URI} - JURY_ADMIN_PASSWORD=${JURY_ADMIN_PASSWORD} - EMAIL_HOST=${EMAIL_HOST} - - EMAIL_PORT=${EMAIL_PORT} + - EMAIL_PORT=${EMAIL_PORT:587} - EMAIL_FROM=${EMAIL_FROM} - EMAIL_FROM_NAME=${EMAIL_FROM_NAME} - EMAIL_USERNAME=${EMAIL_USERNAME} From 0f5fb7281da86c607cf8126843cccf3c374f0776 Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Wed, 28 Aug 2024 09:48:45 -0500 Subject: [PATCH 4/5] Remove loading popup on fail upload judge --- client/src/components/admin/add-judges/NewJudgeForm.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/components/admin/add-judges/NewJudgeForm.tsx b/client/src/components/admin/add-judges/NewJudgeForm.tsx index 9c31fcb..03f74d7 100644 --- a/client/src/components/admin/add-judges/NewJudgeForm.tsx +++ b/client/src/components/admin/add-judges/NewJudgeForm.tsx @@ -28,6 +28,7 @@ const NewJudgeForm = () => { const res = await postRequest('/judge/new', 'admin', newdata); if (res.status !== 200) { errorAlert(res); + setIsSubmitting(false); return; } From 46c3194169848b4af1952693d208695509f321be Mon Sep 17 00:00:00 2001 From: Michael Zhao Date: Wed, 28 Aug 2024 10:40:22 -0500 Subject: [PATCH 5/5] Fixed judge seen on stats panel --- .../admin/add-judges/AddJudgeStatsPanel.tsx | 6 +++--- server/database/admin.go | 13 ++++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/client/src/components/admin/add-judges/AddJudgeStatsPanel.tsx b/client/src/components/admin/add-judges/AddJudgeStatsPanel.tsx index fd26cfa..f591d8c 100644 --- a/client/src/components/admin/add-judges/AddJudgeStatsPanel.tsx +++ b/client/src/components/admin/add-judges/AddJudgeStatsPanel.tsx @@ -5,12 +5,12 @@ import { errorAlert } from '../../../util'; interface JudgeStats { num: number; - avg_votes: number; + avg_seen: number; num_active: number; } const AddJudgeStatsPanel = () => { - const [stats, setStats] = useState({ num: 0, avg_votes: 0, num_active: 0 }); + const [stats, setStats] = useState({ num: 0, avg_seen: 0, num_active: 0 }); useEffect(() => { const fetchStats = async () => { const res = await getRequest('/judge/stats', 'admin'); @@ -31,7 +31,7 @@ const AddJudgeStatsPanel = () => {
- +
diff --git a/server/database/admin.go b/server/database/admin.go index 27cf50a..f76cfd8 100644 --- a/server/database/admin.go +++ b/server/database/admin.go @@ -41,10 +41,13 @@ func AggregateStats(db *mongo.Database) (*models.Stats, error) { // Get the first document from the cursor var projAvgSeen AvgSeenAgg projCursor.Next(context.Background()) - projCursor.Decode(&projAvgSeen) + err = projCursor.Decode(&projAvgSeen) + if err != nil { + return nil, err + } // Get the average judge seen using an aggregation pipeline - judgeCursor, err := db.Collection("judge").Aggregate(context.Background(), []gin.H{ + judgeCursor, err := db.Collection("judges").Aggregate(context.Background(), []gin.H{ {"$match": gin.H{"active": true}}, {"$group": gin.H{ "_id": nil, @@ -60,7 +63,11 @@ func AggregateStats(db *mongo.Database) (*models.Stats, error) { // Get the first document from the cursor var judgeAvgSeen AvgSeenAgg judgeCursor.Next(context.Background()) - judgeCursor.Decode(&judgeAvgSeen) + err = judgeCursor.Decode(&judgeAvgSeen) + println(judgeAvgSeen.AvgSeen) + if err != nil { + return nil, err + } // Create the stats object var stats models.Stats