diff --git a/campaigns.go b/campaigns.go index 04a5d6c..69590d4 100644 --- a/campaigns.go +++ b/campaigns.go @@ -88,6 +88,7 @@ func handleGetCampaigns(c echo.Context) error { err := app.Queries.QueryCampaigns.Select(&out.Results, id, pq.StringArray(status), query, pg.Offset, pg.Limit) if err != nil { + app.Logger.Printf("error fetching campaigns: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error fetching campaigns: %s", pqErrMsg(err))) } @@ -112,6 +113,7 @@ func handleGetCampaigns(c echo.Context) error { // Lazy load stats. if err := out.Results.LoadStats(app.Queries.GetCampaignStats); err != nil { + app.Logger.Printf("error fetching campaign stats: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error fetching campaign stats: %v", pqErrMsg(err))) } @@ -148,6 +150,7 @@ func handlePreviewCampaign(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "Campaign not found.") } + app.Logger.Printf("error fetching campaign: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error fetching campaign: %s", pqErrMsg(err))) } @@ -159,6 +162,7 @@ func handlePreviewCampaign(c echo.Context) error { // There's no subscriber. Mock one. sub = dummySubscriber } else { + app.Logger.Printf("error fetching subscriber: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error fetching subscriber: %s", pqErrMsg(err))) } @@ -170,6 +174,7 @@ func handlePreviewCampaign(c echo.Context) error { } if err := camp.CompileTemplate(app.Manager.TemplateFuncs(camp)); err != nil { + app.Logger.Printf("error compiling template: %v", err) return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Error compiling template: %v", err)) } @@ -177,6 +182,7 @@ func handlePreviewCampaign(c echo.Context) error { // Render the message body. m := app.Manager.NewMessage(camp, &sub) if err := m.Render(); err != nil { + app.Logger.Printf("error rendering message: %v", err) return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Error rendering message: %v", err)) } @@ -219,7 +225,7 @@ func handleCreateCampaign(c echo.Context) error { uu, err := uuid.NewV4() if err != nil { - app.Logger.Println("error generating UUID: %v", err) + app.Logger.Printf("error generating UUID: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, "Error generating UUID") } @@ -244,6 +250,7 @@ func handleCreateCampaign(c echo.Context) error { "There aren't any subscribers in the target lists to create the campaign.") } + app.Logger.Printf("error creating campaign: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error creating campaign: %v", pqErrMsg(err))) } @@ -272,6 +279,7 @@ func handleUpdateCampaign(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "Campaign not found.") } + app.Logger.Printf("error fetching campaign: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error fetching campaign: %s", pqErrMsg(err))) } @@ -305,6 +313,7 @@ func handleUpdateCampaign(c echo.Context) error { o.TemplateID, o.ListIDs) if err != nil { + app.Logger.Printf("error updating campaign: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error updating campaign: %s", pqErrMsg(err))) } @@ -333,6 +342,7 @@ func handleUpdateCampaignStatus(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "Campaign not found.") } + app.Logger.Printf("error fetching campaign: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error fetching campaign: %s", pqErrMsg(err))) } @@ -377,8 +387,9 @@ func handleUpdateCampaignStatus(c echo.Context) error { res, err := app.Queries.UpdateCampaignStatus.Exec(cm.ID, o.Status) if err != nil { + app.Logger.Printf("error updating campaign status: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, - fmt.Sprintf("Error updating campaign: %s", pqErrMsg(err))) + fmt.Sprintf("Error updating campaign status: %s", pqErrMsg(err))) } if n, _ := res.RowsAffected(); n == 0 { @@ -406,6 +417,7 @@ func handleDeleteCampaign(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "Campaign not found.") } + app.Logger.Printf("error fetching campaign: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error fetching campaign: %s", pqErrMsg(err))) } @@ -418,6 +430,7 @@ func handleDeleteCampaign(c echo.Context) error { } if _, err := app.Queries.DeleteCampaign.Exec(cm.ID); err != nil { + app.Logger.Printf("error deleting campaign: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error deleting campaign: %v", pqErrMsg(err))) } @@ -437,6 +450,7 @@ func handleGetRunningCampaignStats(c echo.Context) error { return c.JSON(http.StatusOK, okResp{[]struct{}{}}) } + app.Logger.Printf("error fetching campaign stats: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error fetching campaign stats: %s", pqErrMsg(err))) } else if len(out) == 0 { @@ -496,6 +510,7 @@ func handleTestCampaign(c echo.Context) error { } var subs models.Subscribers if err := app.Queries.GetSubscribersByEmails.Select(&subs, req.SubscriberEmails); err != nil { + app.Logger.Printf("error fetching subscribers: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error fetching subscribers: %s", pqErrMsg(err))) } else if len(subs) == 0 { @@ -508,6 +523,8 @@ func handleTestCampaign(c echo.Context) error { if err == sql.ErrNoRows { return echo.NewHTTPError(http.StatusBadRequest, "Campaign not found.") } + + app.Logger.Printf("error fetching campaign: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error fetching campaign: %s", pqErrMsg(err))) } @@ -522,7 +539,8 @@ func handleTestCampaign(c echo.Context) error { for _, s := range subs { sub := s if err := sendTestMessage(&sub, &camp, app); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Error sending test: %v", err)) + return echo.NewHTTPError(http.StatusBadRequest, + fmt.Sprintf("Error sending test: %v", err)) } } @@ -532,12 +550,14 @@ func handleTestCampaign(c echo.Context) error { // sendTestMessage takes a campaign and a subsriber and sends out a sample campaign message. func sendTestMessage(sub *models.Subscriber, camp *models.Campaign, app *App) error { if err := camp.CompileTemplate(app.Manager.TemplateFuncs(camp)); err != nil { + app.Logger.Printf("error compiling template: %v", err) return fmt.Errorf("Error compiling template: %v", err) } // Render the message body. m := app.Manager.NewMessage(camp, sub) if err := m.Render(); err != nil { + app.Logger.Printf("error rendering message: %v", err) return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Error rendering message: %v", err)) } diff --git a/lists.go b/lists.go index 344bc97..e5ea778 100644 --- a/lists.go +++ b/lists.go @@ -39,6 +39,7 @@ func handleGetLists(c echo.Context) error { err := app.Queries.GetLists.Select(&out.Results, listID, pg.Offset, pg.Limit) if err != nil { + app.Logger.Printf("error fetching lists: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error fetching lists: %s", pqErrMsg(err))) } @@ -64,7 +65,6 @@ func handleGetLists(c echo.Context) error { out.Total = out.Results[0].Total out.Page = pg.Page out.PerPage = pg.PerPage - return c.JSON(http.StatusOK, okResp{out}) } @@ -87,7 +87,7 @@ func handleCreateList(c echo.Context) error { uu, err := uuid.NewV4() if err != nil { - app.Logger.Println("error generating UUID: %v", err) + app.Logger.Printf("error generating UUID: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, "Error generating UUID") } @@ -100,6 +100,7 @@ func handleCreateList(c echo.Context) error { o.Type, o.Optin, pq.StringArray(normalizeTags(o.Tags))); err != nil { + app.Logger.Printf("error creating list: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error creating list: %s", pqErrMsg(err))) } @@ -127,8 +128,10 @@ func handleUpdateList(c echo.Context) error { return err } - res, err := app.Queries.UpdateList.Exec(id, o.Name, o.Type, o.Optin, pq.StringArray(normalizeTags(o.Tags))) + res, err := app.Queries.UpdateList.Exec(id, + o.Name, o.Type, o.Optin, pq.StringArray(normalizeTags(o.Tags))) if err != nil { + app.Logger.Printf("error updating list: %v", err) return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Error updating list: %s", pqErrMsg(err))) } @@ -163,8 +166,9 @@ func handleDeleteLists(c echo.Context) error { } if _, err := app.Queries.DeleteLists.Exec(ids); err != nil { + app.Logger.Printf("error deleting lists: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, - fmt.Sprintf("Delete failed: %v", err)) + fmt.Sprintf("Error deleting: %v", err)) } return c.JSON(http.StatusOK, okResp{true}) diff --git a/media.go b/media.go index cbabbb7..b4d3951 100644 --- a/media.go +++ b/media.go @@ -10,13 +10,19 @@ import ( "github.com/labstack/echo" ) -var imageMimes = []string{"image/jpg", "image/jpeg", "image/png", "image/svg", "image/gif"} - const ( thumbPrefix = "thumb_" thumbnailSize = 90 ) +// imageMimes is the list of image types allowed to be uploaded. +var imageMimes = []string{ + "image/jpg", + "image/jpeg", + "image/png", + "image/svg", + "image/gif"} + // handleUploadMedia handles media file uploads. func handleUploadMedia(c echo.Context) error { var ( @@ -28,15 +34,17 @@ func handleUploadMedia(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid file uploaded: %v", err)) } + // Validate MIME type with the list of allowed types. var typ = file.Header.Get("Content-type") - ok := validateMIME(typ, imageMimes) - if !ok { + if ok := validateMIME(typ, imageMimes); !ok { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported file type (%s) uploaded.", typ)) } + // Generate filename fName := generateFileName(file.Filename) + // Read file contents in memory src, err := file.Open() if err != nil { @@ -44,9 +52,11 @@ func handleUploadMedia(c echo.Context) error { fmt.Sprintf("Error reading file: %s", err)) } defer src.Close() + // Upload the file. fName, err = app.Media.Put(fName, typ, src) if err != nil { + app.Logger.Printf("error uploading file: %v", err) cleanUp = true return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error uploading file: %s", err)) @@ -65,27 +75,30 @@ func handleUploadMedia(c echo.Context) error { thumbFile, err := createThumbnail(file) if err != nil { cleanUp = true + app.Logger.Printf("error resizing image: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, - fmt.Sprintf("Error opening image for resizing: %s", err)) + fmt.Sprintf("Error resizing image: %s", err)) } // Upload thumbnail. thumbfName, err := app.Media.Put(thumbPrefix+fName, typ, thumbFile) if err != nil { cleanUp = true + app.Logger.Printf("error saving thumbnail: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error saving thumbnail: %s", err)) } uu, err := uuid.NewV4() if err != nil { - app.Logger.Println("error generating UUID: %v", err) + app.Logger.Printf("error generating UUID: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, "Error generating UUID") } // Write to the DB. if _, err := app.Queries.InsertMedia.Exec(uu, fName, thumbfName, 0, 0); err != nil { cleanUp = true + app.Logger.Printf("error inserting uploaded file to db: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error saving uploaded file to db: %s", pqErrMsg(err))) } @@ -131,6 +144,5 @@ func handleDeleteMedia(c echo.Context) error { app.Media.Delete(m.Filename) app.Media.Delete(thumbPrefix + m.Filename) - return c.JSON(http.StatusOK, okResp{true}) } diff --git a/public.go b/public.go index 100406b..14d87b9 100644 --- a/public.go +++ b/public.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "database/sql" "html/template" "image" "image/png" @@ -113,6 +114,7 @@ func handleSubscriptionPage(c echo.Context) error { makeMsgTpl("Error", "", `Error processing request. Please retry.`)) } + return c.Render(http.StatusOK, tplMessage, makeMsgTpl("Unsubscribed", "", `You have been successfully unsubscribed.`)) @@ -232,7 +234,10 @@ func handleLinkRedirect(c echo.Context) error { var url string if err := app.Queries.RegisterLinkClick.Get(&url, linkUUID, campUUID, subUUID); err != nil { - app.Logger.Printf("error fetching redirect link: %s", err) + if err != sql.ErrNoRows { + app.Logger.Printf("error fetching redirect link: %s", err) + } + return c.Render(http.StatusInternalServerError, tplMessage, makeMsgTpl("Error opening link", "", "There was an error opening the link. Please try later.")) @@ -269,8 +274,7 @@ func handleSelfExportSubscriberData(c echo.Context) error { // Is export allowed? if !app.Constants.Privacy.AllowExport { return c.Render(http.StatusBadRequest, tplMessage, - makeMsgTpl("Invalid request", "", - "The feature is not available.")) + makeMsgTpl("Invalid request", "", "The feature is not available.")) } // Get the subscriber's data. A single query that gets the profile, @@ -287,7 +291,8 @@ func handleSelfExportSubscriberData(c echo.Context) error { // Send the data out to the subscriber as an atachment. var msg bytes.Buffer if err := app.NotifTpls.ExecuteTemplate(&msg, notifSubscriberData, data); err != nil { - app.Logger.Printf("error compiling notification template '%s': %v", notifSubscriberData, err) + app.Logger.Printf("error compiling notification template '%s': %v", + notifSubscriberData, err) return c.Render(http.StatusInternalServerError, tplMessage, makeMsgTpl("Error preparing data", "", "There was an error preparing your data. Please try later.")) diff --git a/subimporter/importer.go b/subimporter/importer.go index 8b87de9..0f60238 100644 --- a/subimporter/importer.go +++ b/subimporter/importer.go @@ -178,7 +178,6 @@ func (im *Importer) getStatus() string { im.RLock() status := im.status.Status im.RUnlock() - return status } @@ -190,7 +189,6 @@ func (im *Importer) isDone() bool { s = false } im.RUnlock() - return s } diff --git a/subscribers.go b/subscribers.go index 1e0020b..e7f6746 100644 --- a/subscribers.go +++ b/subscribers.go @@ -76,6 +76,7 @@ func handleGetSubscriber(c echo.Context) error { err := app.Queries.GetSubscriber.Select(&out, id, nil) if err != nil { + app.Logger.Printf("error fetching subscriber: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error fetching subscriber: %s", pqErrMsg(err))) } @@ -83,7 +84,9 @@ func handleGetSubscriber(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "Subscriber not found.") } if err := out.LoadLists(app.Queries.GetSubscriberListsLazy); err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Error loading lists for subscriber.") + app.Logger.Printf("error loading subscriber lists: %v", err) + return echo.NewHTTPError(http.StatusInternalServerError, + "Error loading subscriber lists.") } return c.JSON(http.StatusOK, okResp{out[0]}) @@ -117,11 +120,13 @@ func handleQuerySubscribers(c echo.Context) error { } stmt := fmt.Sprintf(app.Queries.QuerySubscribers, cond) + // Create a readonly transaction to prevent mutations. tx, err := app.DB.BeginTxx(context.Background(), &sql.TxOptions{ReadOnly: true}) if err != nil { + app.Logger.Printf("error preparing subscriber query: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, - fmt.Sprintf("Error preparing query: %v", pqErrMsg(err))) + fmt.Sprintf("Error preparing subscriber query: %v", pqErrMsg(err))) } defer tx.Rollback() @@ -133,6 +138,7 @@ func handleQuerySubscribers(c echo.Context) error { // Lazy load lists for each subscriber. if err := out.Results.LoadLists(app.Queries.GetSubscriberListsLazy); err != nil { + app.Logger.Printf("error fetching subscriber lists: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error fetching subscriber lists: %v", pqErrMsg(err))) } @@ -211,10 +217,10 @@ func handleUpdateSubscriber(c echo.Context) error { req.Status, req.Attribs, req.Lists) - if err != nil { + app.Logger.Printf("error updating subscriber: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, - fmt.Sprintf("Update failed: %v", pqErrMsg(err))) + fmt.Sprintf("Error updating subscriber: %v", pqErrMsg(err))) } return handleGetSubscriber(c) @@ -235,6 +241,7 @@ func handleGetSubscriberSendOptin(c echo.Context) error { // Fetch the subscriber. err := app.Queries.GetSubscriber.Select(&out, id, nil) if err != nil { + app.Logger.Printf("error fetching subscriber: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error fetching subscriber: %s", pqErrMsg(err))) } @@ -281,6 +288,7 @@ func handleBlacklistSubscribers(c echo.Context) error { } if _, err := app.Queries.BlacklistSubscribers.Exec(IDs); err != nil { + app.Logger.Printf("error blacklisting subscribers: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error blacklisting: %v", err)) } @@ -337,6 +345,7 @@ func handleManageSubscriberLists(c echo.Context) error { } if err != nil { + app.Logger.Printf("error updating subscriptions: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Error processing lists: %v", err)) } @@ -375,8 +384,9 @@ func handleDeleteSubscribers(c echo.Context) error { } if _, err := app.Queries.DeleteSubscribers.Exec(IDs, nil); err != nil { + app.Logger.Printf("error deleting subscribers: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, - fmt.Sprintf("Error deleting: %v", err)) + fmt.Sprintf("Error deleting subscribers: %v", err)) } return c.JSON(http.StatusOK, okResp{true}) @@ -398,6 +408,7 @@ func handleDeleteSubscribersByQuery(c echo.Context) error { app.Queries.DeleteSubscribersByQuery, req.ListIDs, app.DB) if err != nil { + app.Logger.Printf("error querying subscribers: %v", err) return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Error: %v", err)) } @@ -421,6 +432,7 @@ func handleBlacklistSubscribersByQuery(c echo.Context) error { app.Queries.BlacklistSubscribersByQuery, req.ListIDs, app.DB) if err != nil { + app.Logger.Printf("error blacklisting subscribers: %v", err) return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Error: %v", err)) } @@ -456,8 +468,10 @@ func handleManageSubscriberListsByQuery(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "Invalid action.") } - err := app.Queries.execSubscriberQueryTpl(sanitizeSQLExp(req.Query), stmt, req.ListIDs, app.DB, req.TargetListIDs) + err := app.Queries.execSubscriberQueryTpl(sanitizeSQLExp(req.Query), + stmt, req.ListIDs, app.DB, req.TargetListIDs) if err != nil { + app.Logger.Printf("error updating subscriptions: %v", err) return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Error: %v", err)) } @@ -514,8 +528,10 @@ func insertSubscriber(req subimporter.SubReq, app *App) (int, error) { if pqErr, ok := err.(*pq.Error); ok && pqErr.Constraint == "subscribers_email_key" { return 0, echo.NewHTTPError(http.StatusBadRequest, "The e-mail already exists.") } + + app.Logger.Printf("error inserting subscriber: %v", err) return 0, echo.NewHTTPError(http.StatusInternalServerError, - fmt.Sprintf("Error creating subscriber: %v", err)) + fmt.Sprintf("Error inserting subscriber: %v", err)) } // If the lists are double-optins, send confirmation e-mails. @@ -541,6 +557,7 @@ func exportSubscriberData(id int64, subUUID string, exportables map[string]bool, uu = subUUID } if err := app.Queries.ExportSubscriberData.Get(&data, id, uu); err != nil { + app.Logger.Printf("error fetching subscriber export data: %v", err) return data, nil, err } @@ -561,6 +578,7 @@ func exportSubscriberData(id int64, subUUID string, exportables map[string]bool, // Marshal the data into an indented payload. b, err := json.MarshalIndent(data, "", " ") if err != nil { + app.Logger.Printf("error marshalling subscriber export data: %v", err) return data, nil, err } return data, b, nil