mirror of
https://github.com/warmbly/warmbly.git
synced 2026-10-06 00:02:15 +00:00
feat: key unibox snoozes by organization as well as user and thread (migration 000259 backfills from each thread's mailbox organization), scope every snooze read and write and the org-transfer scope to it, and resolve unibox list cursors only within the caller's organization or user
This commit is contained in:
@@ -688,8 +688,13 @@ func (h *Handler) CreateUniboxSnooze(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
orgID := middleware.GetOrganizationID(c)
|
||||
if orgID == nil {
|
||||
errx.Handle(c, errx.ErrNoOrganization)
|
||||
return
|
||||
}
|
||||
threads := req.threads()
|
||||
rows, xerr := h.UniboxService.Snooze(c.Request.Context(), uid, threads, req.SnoozedUntil)
|
||||
rows, xerr := h.UniboxService.Snooze(c.Request.Context(), *orgID, uid, threads, req.SnoozedUntil)
|
||||
if xerr != nil {
|
||||
errx.Handle(c, xerr)
|
||||
return
|
||||
@@ -734,7 +739,12 @@ func (h *Handler) DeleteUniboxSnooze(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if xerr := h.UniboxService.Unsnooze(c.Request.Context(), uid, threads); xerr != nil {
|
||||
orgID := middleware.GetOrganizationID(c)
|
||||
if orgID == nil {
|
||||
errx.Handle(c, errx.ErrNoOrganization)
|
||||
return
|
||||
}
|
||||
if xerr := h.UniboxService.Unsnooze(c.Request.Context(), *orgID, uid, threads); xerr != nil {
|
||||
errx.Handle(c, xerr)
|
||||
return
|
||||
}
|
||||
@@ -827,7 +837,12 @@ func (h *Handler) ListUniboxSnoozes(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
resp, xerr := h.UniboxService.ListSnoozes(c.Request.Context(), uid)
|
||||
orgID := middleware.GetOrganizationID(c)
|
||||
if orgID == nil {
|
||||
errx.Handle(c, errx.ErrNoOrganization)
|
||||
return
|
||||
}
|
||||
resp, xerr := h.UniboxService.ListSnoozes(c.Request.Context(), *orgID, uid)
|
||||
if xerr != nil {
|
||||
errx.Handle(c, xerr)
|
||||
return
|
||||
|
||||
@@ -175,7 +175,7 @@ func (d Deps) snoozeThread(ctx context.Context, inv Invocation, args json.RawMes
|
||||
if perr != nil {
|
||||
return "", ErrInvalidArgs
|
||||
}
|
||||
snooze, xerr := d.Unibox.Snooze(ctx, inv.UserID, []string{in.ThreadID}, until)
|
||||
snooze, xerr := d.Unibox.Snooze(ctx, inv.OrgID, inv.UserID, []string{in.ThreadID}, until)
|
||||
if xerr != nil {
|
||||
return "", fromErrx(xerr)
|
||||
}
|
||||
@@ -196,7 +196,7 @@ func (d Deps) unsnoozeThread(ctx context.Context, inv Invocation, args json.RawM
|
||||
if in.ThreadID == "" {
|
||||
return "", ErrInvalidArgs
|
||||
}
|
||||
if xerr := d.Unibox.Unsnooze(ctx, inv.UserID, []string{in.ThreadID}); xerr != nil {
|
||||
if xerr := d.Unibox.Unsnooze(ctx, inv.OrgID, inv.UserID, []string{in.ThreadID}); xerr != nil {
|
||||
return "", fromErrx(xerr)
|
||||
}
|
||||
d.logAudit(ctx, inv, models.AuditActionUpdate, models.AuditEntityUnibox, nil, nil)
|
||||
|
||||
@@ -105,7 +105,6 @@ const (
|
||||
orgCampaigns = `(SELECT id FROM campaigns WHERE organization_id = $1)`
|
||||
orgContacts = `(SELECT id FROM contacts WHERE organization_id = $1)`
|
||||
orgTasks = `(SELECT id FROM tasks WHERE email_account_id IN ` + orgMailboxes + ` AND task_type <> 'placement')`
|
||||
orgThreads = `(SELECT DISTINCT thread_id FROM unibox_emails WHERE email_id IN ` + orgMailboxes + `)`
|
||||
orgPipelines = `(SELECT id FROM pipelines WHERE organization_id = $1)`
|
||||
orgInvitations = `(SELECT id FROM organization_invitations WHERE organization_id = $1)`
|
||||
orgTeams = `(SELECT id FROM teams WHERE organization_id = $1)`
|
||||
@@ -713,7 +712,7 @@ var Tables = []Table{
|
||||
},
|
||||
{
|
||||
Name: "unibox_snoozes", Group: models.OrgDataGroupInbox,
|
||||
Scope: `thread_id IN ` + orgThreads,
|
||||
Scope: scopeOrg,
|
||||
},
|
||||
{
|
||||
Name: "inbox_tag_results", Group: models.OrgDataGroupInbox,
|
||||
|
||||
@@ -52,9 +52,9 @@ type UniboxService interface {
|
||||
|
||||
// Snooze hides conversations until `until`. Unsnooze drops the rows. Both
|
||||
// take a set so the list's selection bar is one call, not one per row.
|
||||
Snooze(ctx context.Context, userID uuid.UUID, threadIDs []string, until time.Time) ([]models.UniboxSnooze, *errx.Error)
|
||||
Unsnooze(ctx context.Context, userID uuid.UUID, threadIDs []string) *errx.Error
|
||||
ListSnoozes(ctx context.Context, userID uuid.UUID) ([]models.UniboxSnooze, *errx.Error)
|
||||
Snooze(ctx context.Context, orgID, userID uuid.UUID, threadIDs []string, until time.Time) ([]models.UniboxSnooze, *errx.Error)
|
||||
Unsnooze(ctx context.Context, orgID, userID uuid.UUID, threadIDs []string) *errx.Error
|
||||
ListSnoozes(ctx context.Context, orgID, userID uuid.UUID) ([]models.UniboxSnooze, *errx.Error)
|
||||
|
||||
// Overview powers the scope rail + top metric strip in one call.
|
||||
Overview(ctx context.Context, orgID, userID uuid.UUID) (*models.UniboxOverview, *errx.Error)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
// Snooze takes a set of conversations so the list's selection bar is one call.
|
||||
// A single thread is the one-element case.
|
||||
func (s *uniboxService) Snooze(ctx context.Context, userID uuid.UUID, threadIDs []string, until time.Time) ([]models.UniboxSnooze, *errx.Error) {
|
||||
func (s *uniboxService) Snooze(ctx context.Context, orgID, userID uuid.UUID, threadIDs []string, until time.Time) ([]models.UniboxSnooze, *errx.Error) {
|
||||
threadIDs = nonEmpty(threadIDs)
|
||||
if len(threadIDs) == 0 {
|
||||
return nil, errx.New(errx.BadRequest, "thread_id is required")
|
||||
@@ -32,7 +32,7 @@ func (s *uniboxService) Snooze(ctx context.Context, userID uuid.UUID, threadIDs
|
||||
return nil, errx.New(errx.BadRequest, "snoozed_until is too far in the future (max 90 days)")
|
||||
}
|
||||
|
||||
rows, err := s.uniboxRepository.UpsertSnoozes(ctx, userID, threadIDs, until.UTC())
|
||||
rows, err := s.uniboxRepository.UpsertSnoozes(ctx, orgID, userID, threadIDs, until.UTC())
|
||||
if err != nil {
|
||||
errs.CaptureException(err)
|
||||
return nil, errx.InternalError()
|
||||
@@ -40,7 +40,7 @@ func (s *uniboxService) Snooze(ctx context.Context, userID uuid.UUID, threadIDs
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func (s *uniboxService) Unsnooze(ctx context.Context, userID uuid.UUID, threadIDs []string) *errx.Error {
|
||||
func (s *uniboxService) Unsnooze(ctx context.Context, orgID, userID uuid.UUID, threadIDs []string) *errx.Error {
|
||||
threadIDs = nonEmpty(threadIDs)
|
||||
if len(threadIDs) == 0 {
|
||||
return errx.New(errx.BadRequest, "thread_id is required")
|
||||
@@ -48,7 +48,7 @@ func (s *uniboxService) Unsnooze(ctx context.Context, userID uuid.UUID, threadID
|
||||
if len(threadIDs) > SnoozeMaxThreads {
|
||||
return errx.ErrSeenMax
|
||||
}
|
||||
if err := s.uniboxRepository.DeleteSnoozes(ctx, userID, threadIDs); err != nil {
|
||||
if err := s.uniboxRepository.DeleteSnoozes(ctx, orgID, userID, threadIDs); err != nil {
|
||||
errs.CaptureException(err)
|
||||
return errx.InternalError()
|
||||
}
|
||||
@@ -67,8 +67,8 @@ func nonEmpty(in []string) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *uniboxService) ListSnoozes(ctx context.Context, userID uuid.UUID) ([]models.UniboxSnooze, *errx.Error) {
|
||||
rows, err := s.uniboxRepository.ListSnoozes(ctx, userID)
|
||||
func (s *uniboxService) ListSnoozes(ctx context.Context, orgID, userID uuid.UUID) ([]models.UniboxSnooze, *errx.Error) {
|
||||
rows, err := s.uniboxRepository.ListSnoozes(ctx, orgID, userID)
|
||||
if err != nil {
|
||||
errs.CaptureException(err)
|
||||
return nil, errx.InternalError()
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
ALTER TABLE public.unibox_snoozes DROP CONSTRAINT IF EXISTS unibox_snoozes_org_user_thread_key;
|
||||
|
||||
-- Keep the latest snooze per (user, thread) so the old key can return.
|
||||
DELETE FROM public.unibox_snoozes s
|
||||
USING public.unibox_snoozes newer
|
||||
WHERE s.user_id = newer.user_id AND s.thread_id = newer.thread_id
|
||||
AND (s.snoozed_until, s.id) < (newer.snoozed_until, newer.id);
|
||||
|
||||
ALTER TABLE public.unibox_snoozes
|
||||
ADD CONSTRAINT unibox_snoozes_user_id_thread_id_key UNIQUE (user_id, thread_id);
|
||||
|
||||
ALTER TABLE public.unibox_snoozes DROP COLUMN organization_id;
|
||||
@@ -0,0 +1,23 @@
|
||||
-- A snooze belongs to the organization whose inbox it hides a thread in.
|
||||
ALTER TABLE public.unibox_snoozes
|
||||
ADD COLUMN organization_id uuid REFERENCES public.organizations (id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE public.unibox_snoozes DROP CONSTRAINT unibox_snoozes_user_id_thread_id_key;
|
||||
|
||||
DELETE FROM public.unibox_snoozes WHERE snoozed_until <= now();
|
||||
|
||||
-- One row per organization the thread's mail sits in, read the way the inbox
|
||||
-- matches a snooze (the mail's user and thread).
|
||||
INSERT INTO public.unibox_snoozes (user_id, thread_id, snoozed_until, created_at, updated_at, organization_id)
|
||||
SELECT DISTINCT s.user_id, s.thread_id, s.snoozed_until, s.created_at, s.updated_at, ea.organization_id
|
||||
FROM public.unibox_snoozes s
|
||||
JOIN public.unibox_emails ue ON ue.user_id = s.user_id AND ue.thread_id = s.thread_id
|
||||
JOIN public.email_accounts ea ON ea.id = ue.email_id
|
||||
WHERE s.organization_id IS NULL AND ea.organization_id IS NOT NULL;
|
||||
|
||||
DELETE FROM public.unibox_snoozes WHERE organization_id IS NULL;
|
||||
|
||||
ALTER TABLE public.unibox_snoozes ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE public.unibox_snoozes
|
||||
ADD CONSTRAINT unibox_snoozes_org_user_thread_key UNIQUE (organization_id, user_id, thread_id);
|
||||
@@ -96,15 +96,15 @@ type UniboxRepository interface {
|
||||
ProcessPendingWarmupVerification(ctx context.Context, id uuid.UUID, process func(*models.JobEventNewEmail) error) error
|
||||
UpdatePendingEmail(ctx context.Context, userID, id uuid.UUID, update func(*models.EmailMessageStoreData)) (bool, error)
|
||||
|
||||
// Snooze: per (user, thread). UpsertSnooze adopts the new
|
||||
// Snooze: per (organization, user, thread). UpsertSnooze adopts the new
|
||||
// snoozed_until even if one already exists; DeleteSnooze removes
|
||||
// the row outright (instant un-snooze). ListSnoozes returns the
|
||||
// active set for the user.
|
||||
// The bulk forms back the conversation list's selection bar, so filing a
|
||||
// screenful of mail is one round trip rather than one per row.
|
||||
UpsertSnoozes(ctx context.Context, userID uuid.UUID, threadIDs []string, until time.Time) ([]models.UniboxSnooze, error)
|
||||
DeleteSnoozes(ctx context.Context, userID uuid.UUID, threadIDs []string) error
|
||||
ListSnoozes(ctx context.Context, userID uuid.UUID) ([]models.UniboxSnooze, error)
|
||||
UpsertSnoozes(ctx context.Context, orgID, userID uuid.UUID, threadIDs []string, until time.Time) ([]models.UniboxSnooze, error)
|
||||
DeleteSnoozes(ctx context.Context, orgID, userID uuid.UUID, threadIDs []string) error
|
||||
ListSnoozes(ctx context.Context, orgID, userID uuid.UUID) ([]models.UniboxSnooze, error)
|
||||
|
||||
// Overview powers the scope rail + top metric strip. Single call
|
||||
// so the client doesn't fan out N+M queries for each mailbox/tag.
|
||||
@@ -356,7 +356,7 @@ func (r *uniboxRepository) GetIncoming(ctx context.Context, userID uuid.UUID, li
|
||||
if err == nil {
|
||||
query += fmt.Sprintf(`
|
||||
AND (internal_date, id) < (
|
||||
SELECT internal_date, id FROM unibox_emails WHERE id = $%d
|
||||
SELECT internal_date, id FROM unibox_emails WHERE id = $%d AND user_id = $1
|
||||
)`, argPos)
|
||||
args = append(args, cursorID)
|
||||
argPos++
|
||||
@@ -471,7 +471,8 @@ func (r *uniboxRepository) GetByThread(ctx context.Context, orgID, emailID uuid.
|
||||
if err == nil {
|
||||
query += fmt.Sprintf(`
|
||||
AND (internal_date, id) > (
|
||||
SELECT internal_date, id FROM unibox_emails WHERE id = $%d
|
||||
SELECT internal_date, id FROM unibox_emails
|
||||
WHERE id = $%d AND email_id IN (SELECT id FROM email_accounts WHERE organization_id = $1)
|
||||
)`, argPos)
|
||||
args = append(args, cursorID)
|
||||
argPos++
|
||||
@@ -564,7 +565,7 @@ func (r *uniboxRepository) GetBySender(ctx context.Context, userID uuid.UUID, se
|
||||
if err == nil {
|
||||
query += fmt.Sprintf(`
|
||||
AND (internal_date, id) < (
|
||||
SELECT internal_date, id FROM unibox_emails WHERE id = $%d
|
||||
SELECT internal_date, id FROM unibox_emails WHERE id = $%d AND user_id = $1
|
||||
)`, argPos)
|
||||
args = append(args, cursorID)
|
||||
argPos++
|
||||
@@ -634,7 +635,7 @@ func (r *uniboxRepository) Search(ctx context.Context, orgID uuid.UUID, params *
|
||||
inner += `
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM unibox_snoozes s
|
||||
WHERE s.user_id = ue.user_id
|
||||
WHERE s.organization_id = $1 AND s.user_id = ue.user_id
|
||||
AND s.thread_id = ue.thread_id
|
||||
AND s.snoozed_until > NOW()
|
||||
)`
|
||||
@@ -642,7 +643,7 @@ func (r *uniboxRepository) Search(ctx context.Context, orgID uuid.UUID, params *
|
||||
inner += `
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM unibox_snoozes s
|
||||
WHERE s.user_id = ue.user_id
|
||||
WHERE s.organization_id = $1 AND s.user_id = ue.user_id
|
||||
AND s.thread_id = ue.thread_id
|
||||
AND s.snoozed_until > NOW()
|
||||
)`
|
||||
@@ -825,7 +826,8 @@ func (r *uniboxRepository) Search(ctx context.Context, orgID uuid.UUID, params *
|
||||
if err == nil {
|
||||
query += fmt.Sprintf(`
|
||||
AND (b.internal_date, b.id) < (
|
||||
SELECT internal_date, id FROM unibox_emails WHERE id = $%d
|
||||
SELECT internal_date, id FROM unibox_emails
|
||||
WHERE id = $%d AND email_id IN (SELECT id FROM email_accounts WHERE organization_id = $1)
|
||||
)`, argPos)
|
||||
args = append(args, cursorID)
|
||||
argPos++
|
||||
@@ -848,7 +850,7 @@ func (r *uniboxRepository) GetUnseenCount(ctx context.Context, orgID uuid.UUID,
|
||||
AND ue.folder = '` + models.FolderInbox + `'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM unibox_snoozes s
|
||||
WHERE s.user_id = ue.user_id
|
||||
WHERE s.organization_id = $1 AND s.user_id = ue.user_id
|
||||
AND s.thread_id = ue.thread_id
|
||||
AND s.snoozed_until > NOW()
|
||||
)
|
||||
@@ -1431,18 +1433,18 @@ func (r *uniboxRepository) LatestMessageIDInThread(ctx context.Context, orgID uu
|
||||
|
||||
// ── Snoozes ────────────────────────────────────────────────────────────
|
||||
|
||||
func (r *uniboxRepository) UpsertSnoozes(ctx context.Context, userID uuid.UUID, threadIDs []string, until time.Time) ([]models.UniboxSnooze, error) {
|
||||
func (r *uniboxRepository) UpsertSnoozes(ctx context.Context, orgID, userID uuid.UUID, threadIDs []string, until time.Time) ([]models.UniboxSnooze, error) {
|
||||
if len(threadIDs) == 0 {
|
||||
return nil, errors.New("threadIDs required")
|
||||
}
|
||||
rows, err := r.db.Query(ctx, `
|
||||
INSERT INTO unibox_snoozes (user_id, thread_id, snoozed_until, created_at, updated_at)
|
||||
SELECT $1, t, $3, NOW(), NOW() FROM unnest($2::text[]) AS t
|
||||
ON CONFLICT (user_id, thread_id) DO UPDATE SET
|
||||
INSERT INTO unibox_snoozes (organization_id, user_id, thread_id, snoozed_until, created_at, updated_at)
|
||||
SELECT $4, $1, t, $3, NOW(), NOW() FROM unnest($2::text[]) AS t
|
||||
ON CONFLICT (organization_id, user_id, thread_id) DO UPDATE SET
|
||||
snoozed_until = EXCLUDED.snoozed_until,
|
||||
updated_at = NOW()
|
||||
RETURNING id, user_id, thread_id, snoozed_until, created_at, updated_at
|
||||
`, userID, threadIDs, until)
|
||||
`, userID, threadIDs, until, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1459,24 +1461,24 @@ func (r *uniboxRepository) UpsertSnoozes(ctx context.Context, userID uuid.UUID,
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (r *uniboxRepository) DeleteSnoozes(ctx context.Context, userID uuid.UUID, threadIDs []string) error {
|
||||
func (r *uniboxRepository) DeleteSnoozes(ctx context.Context, orgID, userID uuid.UUID, threadIDs []string) error {
|
||||
if len(threadIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
_, err := r.db.Exec(ctx,
|
||||
`DELETE FROM unibox_snoozes WHERE user_id = $1 AND thread_id = ANY($2)`,
|
||||
userID, threadIDs,
|
||||
`DELETE FROM unibox_snoozes WHERE organization_id = $3 AND user_id = $1 AND thread_id = ANY($2)`,
|
||||
userID, threadIDs, orgID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *uniboxRepository) ListSnoozes(ctx context.Context, userID uuid.UUID) ([]models.UniboxSnooze, error) {
|
||||
func (r *uniboxRepository) ListSnoozes(ctx context.Context, orgID, userID uuid.UUID) ([]models.UniboxSnooze, error) {
|
||||
rows, err := r.db.Query(ctx, `
|
||||
SELECT id, user_id, thread_id, snoozed_until, created_at, updated_at
|
||||
FROM unibox_snoozes
|
||||
WHERE user_id = $1 AND snoozed_until > NOW()
|
||||
WHERE organization_id = $2 AND user_id = $1 AND snoozed_until > NOW()
|
||||
ORDER BY snoozed_until ASC
|
||||
`, userID)
|
||||
`, userID, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1532,7 +1534,7 @@ func (r *uniboxRepository) Overview(ctx context.Context, orgID uuid.UUID) (*mode
|
||||
e.folder = 'archive' AS is_archived,
|
||||
EXISTS (
|
||||
SELECT 1 FROM unibox_snoozes s
|
||||
WHERE s.user_id = e.user_id
|
||||
WHERE s.organization_id = $1 AND s.user_id = e.user_id
|
||||
AND s.thread_id = e.thread_id
|
||||
AND s.snoozed_until > NOW()
|
||||
) AS is_snoozed
|
||||
@@ -1599,7 +1601,7 @@ func (r *uniboxRepository) Overview(ctx context.Context, orgID uuid.UUID) (*mode
|
||||
WHERE e.email_id IN (SELECT id FROM email_accounts WHERE organization_id = $1)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM unibox_snoozes s
|
||||
WHERE s.user_id = e.user_id
|
||||
WHERE s.organization_id = $1 AND s.user_id = e.user_id
|
||||
AND s.thread_id = e.thread_id
|
||||
AND s.snoozed_until > NOW()
|
||||
)
|
||||
@@ -1636,12 +1638,12 @@ func (r *uniboxRepository) Overview(ctx context.Context, orgID uuid.UUID) (*mode
|
||||
COUNT(DISTINCT COALESCE(NULLIF(ue.thread_id, ''), ue.id::text)) FILTER (WHERE ue.id IS NOT NULL AND NOT ue.seen
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM unibox_snoozes s
|
||||
WHERE s.user_id = ea.user_id AND s.thread_id = ue.thread_id AND s.snoozed_until > NOW()
|
||||
WHERE s.organization_id = $1 AND s.user_id = ea.user_id AND s.thread_id = ue.thread_id AND s.snoozed_until > NOW()
|
||||
)) AS unread,
|
||||
COUNT(DISTINCT COALESCE(NULLIF(ue.thread_id, ''), ue.id::text)) FILTER (WHERE ue.id IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM unibox_snoozes s
|
||||
WHERE s.user_id = ea.user_id AND s.thread_id = ue.thread_id AND s.snoozed_until > NOW()
|
||||
WHERE s.organization_id = $1 AND s.user_id = ea.user_id AND s.thread_id = ue.thread_id AND s.snoozed_until > NOW()
|
||||
)) AS total
|
||||
FROM email_accounts ea
|
||||
LEFT JOIN unibox_emails ue ON ue.email_id = ea.id AND ue.user_id = ea.user_id
|
||||
@@ -1678,12 +1680,12 @@ func (r *uniboxRepository) Overview(ctx context.Context, orgID uuid.UUID) (*mode
|
||||
COUNT(DISTINCT COALESCE(NULLIF(ue.thread_id, ''), ue.id::text)) FILTER (WHERE ue.id IS NOT NULL AND NOT ue.seen
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM unibox_snoozes s
|
||||
WHERE s.user_id = ue.user_id AND s.thread_id = ue.thread_id AND s.snoozed_until > NOW()
|
||||
WHERE s.organization_id = $1 AND s.user_id = ue.user_id AND s.thread_id = ue.thread_id AND s.snoozed_until > NOW()
|
||||
)) AS unread,
|
||||
COUNT(DISTINCT COALESCE(NULLIF(ue.thread_id, ''), ue.id::text)) FILTER (WHERE ue.id IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM unibox_snoozes s
|
||||
WHERE s.user_id = ue.user_id AND s.thread_id = ue.thread_id AND s.snoozed_until > NOW()
|
||||
WHERE s.organization_id = $1 AND s.user_id = ue.user_id AND s.thread_id = ue.thread_id AND s.snoozed_until > NOW()
|
||||
)) AS total
|
||||
FROM tags t
|
||||
LEFT JOIN email_tags et ON et.tag_id = t.id
|
||||
@@ -1726,7 +1728,7 @@ func (r *uniboxRepository) Overview(ctx context.Context, orgID uuid.UUID) (*mode
|
||||
AND e.folder NOT IN `+foldersOutsideWorkingViews+`
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM unibox_snoozes s
|
||||
WHERE s.user_id = e.user_id AND s.thread_id = e.thread_id AND s.snoozed_until > NOW()
|
||||
WHERE s.organization_id = $1 AND s.user_id = e.user_id AND s.thread_id = e.thread_id AND s.snoozed_until > NOW()
|
||||
)
|
||||
GROUP BY e.thread_id
|
||||
)
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestLiveUniboxNotificationBadgeCountsOnlyWhatInboxLists(t *testing.T) {
|
||||
f.scopedMessage(t, repo, "thread-sent", "them@example.com", models.FolderSent, now)
|
||||
f.scopedMessage(t, repo, "thread-draft", "them@example.com", models.FolderDrafts, now)
|
||||
f.scopedMessage(t, repo, "thread-snoozed", "them@example.com", models.FolderInbox, now)
|
||||
if _, err := repo.UpsertSnoozes(ctx, f.user, []string{"thread-snoozed"}, now.Add(24*time.Hour)); err != nil {
|
||||
if _, err := repo.UpsertSnoozes(ctx, f.org, f.user, []string{"thread-snoozed"}, now.Add(24*time.Hour)); err != nil {
|
||||
t.Fatalf("UpsertSnoozes: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -304,10 +304,10 @@ func TestLiveUniboxScopeSnoozeTakesASet(t *testing.T) {
|
||||
f.scopedMessage(t, repo, "thread-a", "them@example.com", models.FolderInbox, now)
|
||||
f.scopedMessage(t, repo, "thread-b", "them@example.com", models.FolderInbox, now)
|
||||
t.Cleanup(func() {
|
||||
_ = repo.DeleteSnoozes(context.Background(), f.user, []string{"thread-a", "thread-b"})
|
||||
_ = repo.DeleteSnoozes(context.Background(), f.org, f.user, []string{"thread-a", "thread-b"})
|
||||
})
|
||||
|
||||
rows, err := repo.UpsertSnoozes(ctx, f.user, []string{"thread-a", "thread-b"}, now.Add(time.Hour))
|
||||
rows, err := repo.UpsertSnoozes(ctx, f.org, f.user, []string{"thread-a", "thread-b"}, now.Add(time.Hour))
|
||||
if err != nil {
|
||||
t.Fatalf("UpsertSnoozes: %v", err)
|
||||
}
|
||||
@@ -324,11 +324,11 @@ func TestLiveUniboxScopeSnoozeTakesASet(t *testing.T) {
|
||||
}
|
||||
|
||||
// A second pass over the same set moves the time instead of erroring.
|
||||
if _, err := repo.UpsertSnoozes(ctx, f.user, []string{"thread-a", "thread-b"}, now.Add(2*time.Hour)); err != nil {
|
||||
if _, err := repo.UpsertSnoozes(ctx, f.org, f.user, []string{"thread-a", "thread-b"}, now.Add(2*time.Hour)); err != nil {
|
||||
t.Fatalf("UpsertSnoozes again: %v", err)
|
||||
}
|
||||
|
||||
if err := repo.DeleteSnoozes(ctx, f.user, []string{"thread-a", "thread-b"}); err != nil {
|
||||
if err := repo.DeleteSnoozes(ctx, f.org, f.user, []string{"thread-a", "thread-b"}); err != nil {
|
||||
t.Fatalf("DeleteSnoozes: %v", err)
|
||||
}
|
||||
back, err := repo.Search(ctx, f.org, &models.MailSearchParams{PageSize: 50})
|
||||
|
||||
Reference in New Issue
Block a user