@Summary Send a custom notification @ID send-a-custom-notification @Security CoderSessionToken @Tags Notifications @Accept json @Produce json @Param request body codersdk.CustomNotificationRequest true "Provide a non-empty title or message" @Success 204 "No Content" @Failure 400 {object} codersdk.Re
(rw http.ResponseWriter, r *http.Request)
| 355 | // @Failure 500 {object} codersdk.Response "Failed to send custom notification" |
| 356 | // @Router /api/v2/notifications/custom [post] |
| 357 | func (api *API) postCustomNotification(rw http.ResponseWriter, r *http.Request) { |
| 358 | var ( |
| 359 | ctx = r.Context() |
| 360 | apiKey = httpmw.APIKey(r) |
| 361 | ) |
| 362 | |
| 363 | // Parse request |
| 364 | var req codersdk.CustomNotificationRequest |
| 365 | if !httpapi.Read(ctx, rw, r, &req) { |
| 366 | return |
| 367 | } |
| 368 | |
| 369 | // Validate request: require `content` and non-empty `title` and `message` |
| 370 | if err := req.Validate(); err != nil { |
| 371 | api.Logger.Error(ctx, "send custom notification: validation failed", slog.Error(err)) |
| 372 | httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ |
| 373 | Message: "Invalid request body", |
| 374 | Detail: err.Error(), |
| 375 | }) |
| 376 | return |
| 377 | } |
| 378 | |
| 379 | // Block system users from sending custom notifications |
| 380 | user, err := api.Database.GetUserByID(ctx, apiKey.UserID) |
| 381 | if err != nil { |
| 382 | api.Logger.Error(ctx, "send custom notification", slog.Error(err)) |
| 383 | httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ |
| 384 | Message: "Failed to send custom notification", |
| 385 | Detail: err.Error(), |
| 386 | }) |
| 387 | return |
| 388 | } |
| 389 | if user.IsSystem { |
| 390 | api.Logger.Error(ctx, "send custom notification: system user is not allowed", |
| 391 | slog.F("id", user.ID.String()), slog.F("name", user.Name)) |
| 392 | httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ |
| 393 | Message: "Forbidden", |
| 394 | Detail: "System users cannot send custom notifications.", |
| 395 | }) |
| 396 | return |
| 397 | } |
| 398 | |
| 399 | if _, err := api.NotificationsEnqueuer.EnqueueWithData( |
| 400 | //nolint:gocritic // We need to be notifier to send the notification. |
| 401 | dbauthz.AsNotifier(ctx), |
| 402 | user.ID, |
| 403 | notifications.TemplateCustomNotification, |
| 404 | map[string]string{ |
| 405 | "custom_title": req.Content.Title, |
| 406 | "custom_message": req.Content.Message, |
| 407 | }, |
| 408 | map[string]any{ |
| 409 | // Current dedupe is done via an hash of (template, user, method, payload, targets, day). |
| 410 | // Include a minute-bucketed timestamp to bypass per-day dedupe for self-sends, |
| 411 | // letting the caller resend identical content the same day (but not more than |
| 412 | // once per minute). |
| 413 | // TODO(ssncferreira): When custom notifications can target multiple users/roles, |
| 414 | // enforce proper deduplication across recipients to reduce noise and prevent spam. |
nothing calls this directly
no test coverage detected