Skip to content

Commit 1522649

Browse files
authored
feat: welcome email release (#291)
* feat: QueueWelcomeEmail api route, handler and service * refactor: pass template data in struct * fix: property names * feat: qr image generation, upload, link generation, and welcome email queue * feat: get slice of attendee userIds, added route to send all welcome emails; fixes: various
1 parent 42db4cf commit 1522649

File tree

18 files changed

+416
-21
lines changed

18 files changed

+416
-21
lines changed

apps/api/.env.dev.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ AUTH_DISCORD_CLIENT_ID=
1313
AUTH_DISCORD_CLIENT_SECRET=
1414
AUTH_DISCORD_REDIRECT_URI="http://localhost:8080/auth/callback"
1515

16+
# CF
17+
CORE_BUCKETS_USER_QRCODES_BASE_URL=
18+
1619
# For cookies
1720
COOKIE_DOMAIN=localhost
1821
COOKIE_SECURE=false

apps/api/cmd/BAT_worker/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func main() {
8282
batRunsRepo := repository.NewBatRunsRepository(database)
8383

8484
sesClient := email.NewSESClient(cfg.AWS.AccessKey, cfg.AWS.AccessKeySecret, cfg.AWS.Region, logger)
85-
emailService := services.NewEmailService(taskQueueClient, sesClient, logger)
85+
emailService := services.NewEmailService(taskQueueClient, sesClient, nil, logger)
8686
batService := services.NewBatService(applicationRepo, eventRepo, userRepo, batRunsRepo, emailService, txm, nil, scheduler, logger)
8787
applicationService := services.NewApplicationService(applicationRepo, userRepo, eventService, emailService, txm, nil, nil, scheduler, logger)
8888

apps/api/cmd/api/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func main() {
8989
userService := services.NewUserService(userRepo, logger)
9090
eventInterestService := services.NewEventInterestService(eventInterestRepo, logger)
9191
eventService := services.NewEventService(eventRepo, userRepo, r2Client, &cfg.CoreBuckets, logger)
92-
emailService := services.NewEmailService(taskQueueClient, sesClient, logger)
92+
emailService := services.NewEmailService(taskQueueClient, sesClient, r2Client, logger)
9393
applicationService := services.NewApplicationService(applicationRepo, userRepo, eventService, emailService, txm, r2Client, &cfg.CoreBuckets, nil, logger)
9494
teamService := services.NewTeamService(teamRepo, teamMemberRepo, teamJoinRequestRepo, eventRepo, txm, logger)
9595
batService := services.NewBatService(applicationRepo, eventRepo, userRepo, batRunsRepo, emailService, txm, taskQueueClient, nil, logger)

apps/api/cmd/email_worker/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func main() {
4141
// Create ses client
4242
sesClient := email.NewSESClient(cfg.AWS.AccessKey, cfg.AWS.AccessKeySecret, cfg.AWS.Region, logger)
4343

44-
emailService := services.NewEmailService(nil, sesClient, logger)
44+
emailService := services.NewEmailService(nil, sesClient, nil, logger)
4545
emailWorker := workers.NewEmailWorker(emailService, logger)
4646

4747
mux := asynq.NewServeMux()

apps/api/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ require (
6363
github.com/mattn/go-isatty v0.0.19 // indirect
6464
github.com/redis/go-redis/v9 v9.7.0 // indirect
6565
github.com/robfig/cron/v3 v3.0.1 // indirect
66+
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
6667
github.com/spf13/cast v1.7.0 // indirect
6768
github.com/sv-tools/openapi v0.4.0 // indirect
6869
go.yaml.in/yaml/v3 v3.0.4 // indirect

apps/api/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUz
133133
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
134134
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
135135
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
136+
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
137+
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
136138
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
137139
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
138140
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

apps/api/internal/api/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ func (api *API) setupRoutes(mw *mw.Middleware) {
155155

156156
// Admin-only
157157
r.With(ensureEventAdmin).Post("/queue-confirmation-email", api.Handlers.Email.QueueConfirmationEmail)
158+
r.With(ensureEventAdmin).Post("/queue-welcome-email", api.Handlers.Email.QueueWelcomeEmail)
159+
r.With(ensureEventAdmin).Post("/send-welcome-emails", api.Handlers.Bat.SendWelcomeEmails)
158160
r.With(ensureEventAdmin).Post("/calc-admissions", api.Handlers.Admission.HandleCalculateAdmissionsRequest)
159161
r.With(ensureEventAdmin).Patch("/transition-waitlisted-applications", api.Handlers.Application.TransitionWaitlistedApplications)
160162
r.With(ensureEventAdmin).Post("/begin-waitlist-transition", api.Handlers.Bat.QueueScheduleWaitlistTransitionTask)

apps/api/internal/api/handlers/bat.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,29 @@ func (h *BatHandler) QueueShutdownWaitlistSchedulerTask(w http.ResponseWriter, r
197197

198198
res.Send(w, http.StatusOK, nil)
199199
}
200+
201+
// Send welcome emails
202+
//
203+
// @Summary Sends welcome emails to attendees
204+
// @Description
205+
// @Tags
206+
//
207+
// @Param eventId path string true "ID of the event"
208+
// @Success 200 "Welcome emails began to queue successfully"
209+
// @Failure 400 {object} res.ErrorResponse "Bad request: invalid event ID"
210+
// @Failure 500 {object} res.ErrorResponse "Server error: failed to begin queuing welcome emails"
211+
// @Router /events/{eventId}/send-welcome-emails [post]
212+
func (h *BatHandler) SendWelcomeEmails(w http.ResponseWriter, r *http.Request) {
213+
eventId, err := web.PathParamToUUID(r, "eventId")
214+
if err != nil {
215+
res.SendError(w, http.StatusBadRequest, res.NewError("invalid_event_id", "The event ID is not valid."))
216+
return
217+
}
218+
219+
err = h.BatService.SendWelcomeEmailToAttendees(r.Context(), eventId)
220+
if err != nil {
221+
res.SendError(w, http.StatusInternalServerError, res.NewError("internal_err", "Failed to create ScheduleWaitlistTransition task."))
222+
}
223+
224+
res.Send(w, http.StatusCreated, nil)
225+
}

apps/api/internal/api/handlers/email.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net/http"
66

77
"github.com/go-playground/validator/v10"
8+
"github.com/google/uuid"
89
"github.com/rs/zerolog"
910
res "github.com/swamphacks/core/apps/api/internal/api/response"
1011
"github.com/swamphacks/core/apps/api/internal/email"
@@ -82,6 +83,18 @@ type QueueConfirmationEmailFields struct {
8283
FirstName string `json:"firstName" validate:"required"`
8384
}
8485

86+
// Queue a Confirmation Email
87+
//
88+
// @Summary Queue a Confirmation Email Request
89+
// @Description Push a Confirmation Email request to the task queue
90+
// @Tags Email
91+
// @Accept json
92+
// @Produce json
93+
// @Param request body QueueConfirmationEmailFields true "Email data"
94+
// @Success 201 {object} string "OK: Email request queued"
95+
// @Failure 400 {object} response.ErrorResponse "Bad request/Malformed request. The email request is potentially invalid."
96+
// @Failure 500 {object} response.ErrorResponse "Server Error: The server went kaput while queueing email sending"
97+
// @Router /email/queue [post]
8598
func (h *EmailHandler) QueueConfirmationEmail(w http.ResponseWriter, r *http.Request) {
8699
var req QueueConfirmationEmailFields
87100
decoder := json.NewDecoder(r.Body)
@@ -103,3 +116,49 @@ func (h *EmailHandler) QueueConfirmationEmail(w http.ResponseWriter, r *http.Req
103116

104117
res.Send(w, http.StatusOK, nil)
105118
}
119+
120+
type QueueWelcomeEmailFields struct {
121+
Email string `json:"email" validate:"required"`
122+
FirstName string `json:"firstName" validate:"required"`
123+
UserId string `json:userId validate:"required"`
124+
}
125+
126+
// Queue a Welcome Email
127+
//
128+
// @Summary Queue a Welcome Email
129+
// @Description Push an Welcome Email request to the task queue
130+
// @Tags Email
131+
// @Accept json
132+
// @Produce json
133+
// @Param request body QueueConfirmationEmailFields true "Email data"
134+
// @Success 201 {object} string "OK: Email request queued"
135+
// @Failure 400 {object} response.ErrorResponse "Bad request/Malformed request. The email request is potentially invalid."
136+
// @Failure 500 {object} response.ErrorResponse "Server Error: The server went kaput while queueing email sending"
137+
func (h *EmailHandler) QueueWelcomeEmail(w http.ResponseWriter, r *http.Request) {
138+
var req QueueWelcomeEmailFields
139+
decoder := json.NewDecoder(r.Body)
140+
decoder.DisallowUnknownFields()
141+
err := decoder.Decode(&req)
142+
if err != nil {
143+
res.SendError(w, http.StatusBadRequest, res.NewError("invalid_request", "Could not parse request body"))
144+
return
145+
}
146+
147+
validate := validator.New()
148+
if err := validate.Struct(req); err != nil {
149+
res.SendError(w, http.StatusBadRequest, res.NewError("invalid_request", err.Error()))
150+
}
151+
152+
parsedUserId, err := uuid.Parse(req.UserId)
153+
if err != nil {
154+
res.SendError(w, http.StatusBadRequest, res.NewError("invalid_request", "userId must be of type uuid"))
155+
return
156+
}
157+
158+
err = h.emailService.QueueWelcomeEmail(r.Context(), req.Email, req.FirstName, parsedUserId)
159+
if err != nil {
160+
res.SendError(w, http.StatusInternalServerError, res.NewError("internal_err", "Hacker email could not be queued."))
161+
}
162+
163+
res.Send(w, http.StatusOK, nil)
164+
}

apps/api/internal/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@ type AWSConfig struct {
4848

4949
type CoreBuckets struct {
5050
Avatars string `env:"USER_AVATARS" envDefault:"core-user-avatars-dev"`
51+
QRCodes string `env:"USER_QRCODES" envDefault:"core-user-qrcodes-dev"`
5152
ApplicationResumes string `env:"APPLICATION_RESUMES" envDefault:"core-application-resumes-dev"`
5253
EventAssets string `env:"EVENT_ASSETS" envDefault:"core-event-assets-dev"`
5354
AvatarsBaseUrl string `env:"USER_AVATARS_BASE_URL"`
55+
QRCodesBaseUrl string `env:"USER_QRCODES_BASE_URL"`
5456
EventAssetsBaseUrl string `env:"EVENT_ASSETS_BASE_URL"`
5557
}
5658

0 commit comments

Comments
 (0)