Send mailing list emails
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -1695,6 +1695,13 @@ type MailingList implements Node {
|
||||
last: Int
|
||||
before: CursorKey
|
||||
): MailingListSubscriberConnection! @goField(forceResolver: true)
|
||||
|
||||
updates(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
): MailingListUpdateConnection! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
enum MailingListSubscriberStatus
|
||||
@@ -1734,6 +1741,51 @@ type MailingListSubscriberEdge {
|
||||
node: MailingListSubscriber!
|
||||
}
|
||||
|
||||
enum MailingListUpdateStatus
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/coredata.MailingListUpdateStatus"
|
||||
) {
|
||||
DRAFT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.MailingListUpdateStatusDraft"
|
||||
)
|
||||
ENQUEUED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.MailingListUpdateStatusEnqueued"
|
||||
)
|
||||
PROCESSING
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.MailingListUpdateStatusProcessing"
|
||||
)
|
||||
SENT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.MailingListUpdateStatusSent"
|
||||
)
|
||||
}
|
||||
|
||||
type MailingListUpdate implements Node {
|
||||
id: ID!
|
||||
title: String!
|
||||
body: String!
|
||||
status: MailingListUpdateStatus!
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
|
||||
type MailingListUpdateConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.MailingListUpdateConnection"
|
||||
) {
|
||||
edges: [MailingListUpdateEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
totalCount: Int! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type MailingListUpdateEdge {
|
||||
cursor: CursorKey!
|
||||
node: MailingListUpdate!
|
||||
}
|
||||
|
||||
type Organization implements Node {
|
||||
id: ID!
|
||||
name: String!
|
||||
@@ -3407,6 +3459,19 @@ type Mutation {
|
||||
deleteTrustCenterAccess(
|
||||
input: DeleteTrustCenterAccessInput!
|
||||
): DeleteTrustCenterAccessPayload!
|
||||
# Compliance News mutations
|
||||
createMailingListUpdate(
|
||||
input: CreateMailingListUpdateInput!
|
||||
): CreateMailingListUpdatePayload!
|
||||
updateMailingListUpdate(
|
||||
input: UpdateMailingListUpdateInput!
|
||||
): UpdateMailingListUpdatePayload!
|
||||
sendMailingListUpdate(
|
||||
input: SendMailingListUpdateInput!
|
||||
): SendMailingListUpdatePayload!
|
||||
deleteMailingListUpdate(
|
||||
input: DeleteMailingListUpdateInput!
|
||||
): DeleteMailingListUpdatePayload!
|
||||
# Mailing List mutations
|
||||
updateMailingList(
|
||||
input: UpdateMailingListInput!
|
||||
@@ -3829,6 +3894,26 @@ type UpdateMailingListPayload {
|
||||
mailingList: MailingList!
|
||||
}
|
||||
|
||||
input CreateMailingListUpdateInput {
|
||||
mailingListId: ID!
|
||||
title: String!
|
||||
body: String!
|
||||
}
|
||||
|
||||
input UpdateMailingListUpdateInput {
|
||||
id: ID!
|
||||
title: String!
|
||||
body: String!
|
||||
}
|
||||
|
||||
input SendMailingListUpdateInput {
|
||||
id: ID!
|
||||
}
|
||||
|
||||
input DeleteMailingListUpdateInput {
|
||||
id: ID!
|
||||
}
|
||||
|
||||
input CreateMailingListSubscriberInput {
|
||||
mailingListId: ID!
|
||||
fullName: String!
|
||||
@@ -4690,6 +4775,22 @@ type DeleteTrustCenterAccessPayload {
|
||||
deletedTrustCenterAccessId: ID!
|
||||
}
|
||||
|
||||
type CreateMailingListUpdatePayload {
|
||||
mailingListUpdate: MailingListUpdate!
|
||||
}
|
||||
|
||||
type UpdateMailingListUpdatePayload {
|
||||
mailingListUpdate: MailingListUpdate!
|
||||
}
|
||||
|
||||
type SendMailingListUpdatePayload {
|
||||
mailingListUpdate: MailingListUpdate!
|
||||
}
|
||||
|
||||
type DeleteMailingListUpdatePayload {
|
||||
deletedMailingListUpdateId: ID!
|
||||
}
|
||||
|
||||
type CreateMailingListSubscriberPayload {
|
||||
mailingListSubscriberEdge: MailingListSubscriberEdge!
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
66
pkg/server/api/console/v1/types/mailing_list_update.go
Normal file
66
pkg/server/api/console/v1/types/mailing_list_update.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type MailingListUpdateConnection struct {
|
||||
TotalCount int
|
||||
Edges []*MailingListUpdateEdge
|
||||
PageInfo *PageInfo
|
||||
|
||||
Resolver any
|
||||
ParentID gid.GID
|
||||
}
|
||||
|
||||
func NewMailingListUpdate(mlu *coredata.MailingListUpdate) *MailingListUpdate {
|
||||
return &MailingListUpdate{
|
||||
ID: mlu.ID,
|
||||
Title: mlu.Title,
|
||||
Body: mlu.Body,
|
||||
Status: mlu.Status,
|
||||
CreatedAt: mlu.CreatedAt,
|
||||
UpdatedAt: mlu.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewMailingListUpdateEdge(mlu *coredata.MailingListUpdate, orderBy coredata.MailingListUpdateOrderField) *MailingListUpdateEdge {
|
||||
return &MailingListUpdateEdge{
|
||||
Cursor: mlu.CursorKey(orderBy),
|
||||
Node: NewMailingListUpdate(mlu),
|
||||
}
|
||||
}
|
||||
|
||||
func NewMailingListUpdateConnection(
|
||||
p *page.Page[*coredata.MailingListUpdate, coredata.MailingListUpdateOrderField],
|
||||
resolver any,
|
||||
mailingListID gid.GID,
|
||||
) *MailingListUpdateConnection {
|
||||
edges := make([]*MailingListUpdateEdge, len(p.Data))
|
||||
for i := range edges {
|
||||
edges[i] = NewMailingListUpdateEdge(p.Data[i], p.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &MailingListUpdateConnection{
|
||||
Edges: edges,
|
||||
PageInfo: NewPageInfo(p),
|
||||
Resolver: resolver,
|
||||
ParentID: mailingListID,
|
||||
}
|
||||
}
|
||||
@@ -446,6 +446,16 @@ type CreateMailingListSubscriberPayload struct {
|
||||
MailingListSubscriberEdge *MailingListSubscriberEdge `json:"mailingListSubscriberEdge"`
|
||||
}
|
||||
|
||||
type CreateMailingListUpdateInput struct {
|
||||
MailingListID gid.GID `json:"mailingListId"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
type CreateMailingListUpdatePayload struct {
|
||||
MailingListUpdate *MailingListUpdate `json:"mailingListUpdate"`
|
||||
}
|
||||
|
||||
type CreateMeasureInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
Name string `json:"name"`
|
||||
@@ -977,6 +987,14 @@ type DeleteMailingListSubscriberPayload struct {
|
||||
DeletedMailingListSubscriberID gid.GID `json:"deletedMailingListSubscriberId"`
|
||||
}
|
||||
|
||||
type DeleteMailingListUpdateInput struct {
|
||||
ID gid.GID `json:"id"`
|
||||
}
|
||||
|
||||
type DeleteMailingListUpdatePayload struct {
|
||||
DeletedMailingListUpdateID gid.GID `json:"deletedMailingListUpdateId"`
|
||||
}
|
||||
|
||||
type DeleteMeasureInput struct {
|
||||
MeasureID gid.GID `json:"measureId"`
|
||||
}
|
||||
@@ -1453,6 +1471,7 @@ type MailingList struct {
|
||||
ID gid.GID `json:"id"`
|
||||
ReplyTo *mail.Addr `json:"replyTo,omitempty"`
|
||||
Subscribers *MailingListSubscriberConnection `json:"subscribers"`
|
||||
Updates *MailingListUpdateConnection `json:"updates"`
|
||||
}
|
||||
|
||||
func (MailingList) IsNode() {}
|
||||
@@ -1475,6 +1494,23 @@ type MailingListSubscriberEdge struct {
|
||||
Node *MailingListSubscriber `json:"node"`
|
||||
}
|
||||
|
||||
type MailingListUpdate struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
Status coredata.MailingListUpdateStatus `json:"status"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (MailingListUpdate) IsNode() {}
|
||||
func (this MailingListUpdate) GetID() gid.GID { return this.ID }
|
||||
|
||||
type MailingListUpdateEdge struct {
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *MailingListUpdate `json:"node"`
|
||||
}
|
||||
|
||||
type Measure struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Category string `json:"category"`
|
||||
@@ -1815,6 +1851,14 @@ type RiskFilter struct {
|
||||
SnapshotID *gid.GID `json:"snapshotId,omitempty"`
|
||||
}
|
||||
|
||||
type SendMailingListUpdateInput struct {
|
||||
ID gid.GID `json:"id"`
|
||||
}
|
||||
|
||||
type SendMailingListUpdatePayload struct {
|
||||
MailingListUpdate *MailingListUpdate `json:"mailingListUpdate"`
|
||||
}
|
||||
|
||||
type SendSigningNotificationsInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
}
|
||||
@@ -2181,6 +2225,16 @@ type UpdateMailingListPayload struct {
|
||||
MailingList *MailingList `json:"mailingList"`
|
||||
}
|
||||
|
||||
type UpdateMailingListUpdateInput struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
type UpdateMailingListUpdatePayload struct {
|
||||
MailingListUpdate *MailingListUpdate `json:"mailingListUpdate"`
|
||||
}
|
||||
|
||||
type UpdateMeasureInput struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/mailman"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/probo"
|
||||
"go.probo.inc/probo/pkg/server/api/authn"
|
||||
@@ -1546,6 +1547,28 @@ func (r *mailingListResolver) Subscribers(ctx context.Context, obj *types.Mailin
|
||||
return types.NewMailingListSubscriberConnection(result, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// Updates is the resolver for the updates field on MailingList.
|
||||
func (r *mailingListResolver) Updates(ctx context.Context, obj *types.MailingList, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.MailingListUpdateConnection, error) {
|
||||
if err := r.authorize(ctx, obj.ID, probo.ActionMailingListUpdateList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.MailingListUpdateOrderField]{
|
||||
Field: coredata.MailingListUpdateOrderFieldUpdatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
result, err := r.mailman.ListMailingListUpdates(ctx, obj.ID, cursor)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot list mailing list updates", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewMailingListUpdateConnection(result, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// TotalCount is the resolver for the totalCount field.
|
||||
func (r *mailingListSubscriberConnectionResolver) TotalCount(ctx context.Context, obj *types.MailingListSubscriberConnection) (int, error) {
|
||||
if err := r.authorize(ctx, obj.ParentID, probo.ActionMailingListSubscriberList); err != nil {
|
||||
@@ -1565,6 +1588,21 @@ func (r *mailingListSubscriberConnectionResolver) TotalCount(ctx context.Context
|
||||
panic(fmt.Errorf("not implemented: TotalCount for parent type %T", obj.Resolver))
|
||||
}
|
||||
|
||||
// TotalCount is the resolver for the totalCount field on MailingListUpdateConnection.
|
||||
func (r *mailingListUpdateConnectionResolver) TotalCount(ctx context.Context, obj *types.MailingListUpdateConnection) (int, error) {
|
||||
if err := r.authorize(ctx, obj.ParentID, probo.ActionMailingListUpdateList); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
count, err := r.mailman.CountMailingListUpdates(ctx, obj.ParentID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot count mailing list updates", log.Error(err))
|
||||
return 0, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// Evidences is the resolver for the evidences field.
|
||||
func (r *measureResolver) Evidences(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.EvidenceOrderBy) (*types.EvidenceConnection, error) {
|
||||
if err := r.authorize(ctx, obj.ID, probo.ActionEvidenceList); err != nil {
|
||||
@@ -2028,6 +2066,88 @@ func (r *mutationResolver) DeleteTrustCenterAccess(ctx context.Context, input ty
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateMailingListUpdate is the resolver for the createMailingListUpdate field.
|
||||
func (r *mutationResolver) CreateMailingListUpdate(ctx context.Context, input types.CreateMailingListUpdateInput) (*types.CreateMailingListUpdatePayload, error) {
|
||||
if err := r.authorize(ctx, input.MailingListID, probo.ActionMailingListUpdateCreate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mlu, err := r.mailman.CreateMailingListUpdate(ctx, input.MailingListID, input.Title, input.Body)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot create mailing list update", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.CreateMailingListUpdatePayload{
|
||||
MailingListUpdate: types.NewMailingListUpdate(mlu),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateMailingListUpdate is the resolver for the updateMailingListUpdate field.
|
||||
func (r *mutationResolver) UpdateMailingListUpdate(ctx context.Context, input types.UpdateMailingListUpdateInput) (*types.UpdateMailingListUpdatePayload, error) {
|
||||
if err := r.authorize(ctx, input.ID, probo.ActionMailingListUpdateUpdate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mlu, err := r.mailman.UpdateMailingListUpdate(ctx, input.ID, input.Title, input.Body)
|
||||
if err != nil {
|
||||
if errors.Is(err, mailman.ErrMailingListUpdateAlreadySent) {
|
||||
return nil, gqlutils.Conflictf(ctx, "mailing list update can only be edited when in draft")
|
||||
}
|
||||
if errors.Is(err, mailman.ErrMailingListUpdateNotFound) {
|
||||
return nil, gqlutils.NotFoundf(ctx, "mailing list update not found")
|
||||
}
|
||||
r.logger.ErrorCtx(ctx, "cannot update mailing list update", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.UpdateMailingListUpdatePayload{
|
||||
MailingListUpdate: types.NewMailingListUpdate(mlu),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SendMailingListUpdate is the resolver for the sendMailingListUpdate field.
|
||||
func (r *mutationResolver) SendMailingListUpdate(ctx context.Context, input types.SendMailingListUpdateInput) (*types.SendMailingListUpdatePayload, error) {
|
||||
if err := r.authorize(ctx, input.ID, probo.ActionMailingListUpdateUpdate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mlu, err := r.mailman.SendMailingListUpdate(ctx, input.ID)
|
||||
if err != nil {
|
||||
if errors.Is(err, mailman.ErrMailingListUpdateAlreadySent) {
|
||||
return nil, gqlutils.Conflictf(ctx, "mailing list update has already been queued for sending")
|
||||
}
|
||||
if errors.Is(err, mailman.ErrMailingListUpdateNotFound) {
|
||||
return nil, gqlutils.NotFoundf(ctx, "mailing list update not found")
|
||||
}
|
||||
r.logger.ErrorCtx(ctx, "cannot queue mailing list update for sending", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.SendMailingListUpdatePayload{
|
||||
MailingListUpdate: types.NewMailingListUpdate(mlu),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteMailingListUpdate is the resolver for the deleteMailingListUpdate field.
|
||||
func (r *mutationResolver) DeleteMailingListUpdate(ctx context.Context, input types.DeleteMailingListUpdateInput) (*types.DeleteMailingListUpdatePayload, error) {
|
||||
if err := r.authorize(ctx, input.ID, probo.ActionMailingListUpdateDelete); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.mailman.DeleteMailingListUpdate(ctx, input.ID); err != nil {
|
||||
if errors.Is(err, mailman.ErrMailingListUpdateNotFound) {
|
||||
return nil, gqlutils.NotFoundf(ctx, "mailing list update not found")
|
||||
}
|
||||
r.logger.ErrorCtx(ctx, "cannot delete mailing list update", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.DeleteMailingListUpdatePayload{
|
||||
DeletedMailingListUpdateID: input.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateMailingList is the resolver for the updateMailingList field.
|
||||
func (r *mutationResolver) UpdateMailingList(ctx context.Context, input types.UpdateMailingListInput) (*types.UpdateMailingListPayload, error) {
|
||||
if err := r.authorize(ctx, input.ID, probo.ActionMailingListUpdate); err != nil {
|
||||
@@ -2051,8 +2171,16 @@ func (r *mutationResolver) CreateMailingListSubscriber(ctx context.Context, inpu
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subscriber, err := r.mailman.CreateSubscriber(ctx, input.MailingListID, input.Email, input.FullName)
|
||||
subscriber, err := r.mailman.CreateSubscriber(
|
||||
ctx,
|
||||
input.MailingListID,
|
||||
input.Email,
|
||||
input.FullName,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, mailman.ErrSubscriberAlreadyExist) {
|
||||
return nil, gqlutils.Conflictf(ctx, "subscriber already exists in this mailing list")
|
||||
}
|
||||
r.logger.ErrorCtx(ctx, "cannot create mailing list subscriber", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
@@ -2069,6 +2197,9 @@ func (r *mutationResolver) DeleteMailingListSubscriber(ctx context.Context, inpu
|
||||
}
|
||||
|
||||
if err := r.mailman.DeleteSubscriber(ctx, input.ID); err != nil {
|
||||
if errors.Is(err, mailman.ErrSubscriberNotFound) {
|
||||
return nil, gqlutils.NotFoundf(ctx, "mailing list subscriber not found")
|
||||
}
|
||||
r.logger.ErrorCtx(ctx, "cannot delete mailing list subscriber", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
@@ -9196,6 +9327,11 @@ func (r *Resolver) MailingListSubscriberConnection() schema.MailingListSubscribe
|
||||
return &mailingListSubscriberConnectionResolver{r}
|
||||
}
|
||||
|
||||
// MailingListUpdateConnection returns schema.MailingListUpdateConnectionResolver implementation.
|
||||
func (r *Resolver) MailingListUpdateConnection() schema.MailingListUpdateConnectionResolver {
|
||||
return &mailingListUpdateConnectionResolver{r}
|
||||
}
|
||||
|
||||
// Measure returns schema.MeasureResolver implementation.
|
||||
func (r *Resolver) Measure() schema.MeasureResolver { return &measureResolver{r} }
|
||||
|
||||
@@ -9432,6 +9568,7 @@ type frameworkResolver struct{ *Resolver }
|
||||
type frameworkConnectionResolver struct{ *Resolver }
|
||||
type mailingListResolver struct{ *Resolver }
|
||||
type mailingListSubscriberConnectionResolver struct{ *Resolver }
|
||||
type mailingListUpdateConnectionResolver struct{ *Resolver }
|
||||
type measureResolver struct{ *Resolver }
|
||||
type measureConnectionResolver struct{ *Resolver }
|
||||
type meetingResolver struct{ *Resolver }
|
||||
|
||||
@@ -90,7 +90,6 @@ func NewMux(
|
||||
r.Use(authn.NewSessionMiddleware(iamSvc, cookieConfig))
|
||||
|
||||
graphqlHandler := NewGraphQLHandler(iamSvc, trustSvc, esignSvc, mailmanSvc, logger, baseURL, cookieConfig, tokenSecret)
|
||||
|
||||
r.Handle("/graphql", graphqlHandler)
|
||||
|
||||
return r
|
||||
@@ -99,4 +98,3 @@ func NewMux(
|
||||
func (r *Resolver) TrustService(ctx context.Context, tenantID gid.TenantID) *trust.TenantService {
|
||||
return r.trust.WithTenant(tenantID)
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,23 @@ type ComplianceFrameworkEdge
|
||||
node: ComplianceFramework!
|
||||
}
|
||||
|
||||
type MailingListUpdate implements Node @nda {
|
||||
id: ID!
|
||||
title: String!
|
||||
body: String!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
|
||||
type MailingListUpdateConnection @nda {
|
||||
edges: [MailingListUpdateEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type MailingListUpdateEdge @nda {
|
||||
cursor: CursorKey!
|
||||
node: MailingListUpdate!
|
||||
}
|
||||
|
||||
enum CountryCode
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.CountryCode") {
|
||||
AD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAD")
|
||||
@@ -581,6 +598,13 @@ type TrustCenter implements Node {
|
||||
last: Int
|
||||
before: CursorKey
|
||||
): ComplianceExternalURLConnection! @goField(forceResolver: true)
|
||||
|
||||
updates(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
): MailingListUpdateConnection! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type ComplianceExternalURL implements Node {
|
||||
@@ -926,6 +950,7 @@ type MailingListSubscriber implements Node
|
||||
model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.MailingListSubscriber"
|
||||
) {
|
||||
id: ID!
|
||||
fullName: String!
|
||||
email: EmailAddr!
|
||||
status: MailingListSubscriberStatus!
|
||||
createdAt: Datetime!
|
||||
|
||||
@@ -175,11 +175,29 @@ type ComplexityRoot struct {
|
||||
MailingListSubscriber struct {
|
||||
CreatedAt func(childComplexity int) int
|
||||
Email func(childComplexity int) int
|
||||
FullName func(childComplexity int) int
|
||||
ID func(childComplexity int) int
|
||||
Status func(childComplexity int) int
|
||||
UpdatedAt func(childComplexity int) int
|
||||
}
|
||||
|
||||
MailingListUpdate struct {
|
||||
Body func(childComplexity int) int
|
||||
ID func(childComplexity int) int
|
||||
Title func(childComplexity int) int
|
||||
UpdatedAt func(childComplexity int) int
|
||||
}
|
||||
|
||||
MailingListUpdateConnection struct {
|
||||
Edges func(childComplexity int) int
|
||||
PageInfo func(childComplexity int) int
|
||||
}
|
||||
|
||||
MailingListUpdateEdge struct {
|
||||
Cursor func(childComplexity int) int
|
||||
Node func(childComplexity int) int
|
||||
}
|
||||
|
||||
Mutation struct {
|
||||
AcceptElectronicSignature func(childComplexity int, input types.AcceptElectronicSignatureInput) int
|
||||
ExportDocumentPDF func(childComplexity int, input types.ExportDocumentPDFInput) int
|
||||
@@ -275,6 +293,7 @@ type ComplexityRoot struct {
|
||||
References func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
|
||||
Slug func(childComplexity int) int
|
||||
TrustCenterFiles func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
|
||||
Updates func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
|
||||
Vendors func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
|
||||
ViewerSubscription func(childComplexity int) int
|
||||
}
|
||||
@@ -417,6 +436,7 @@ type TrustCenterResolver interface {
|
||||
TrustCenterFiles(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.TrustCenterFileConnection, error)
|
||||
ComplianceFrameworks(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.ComplianceFrameworkConnection, error)
|
||||
ExternalUrls(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.ComplianceExternalURLConnection, error)
|
||||
Updates(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.MailingListUpdateConnection, error)
|
||||
}
|
||||
type TrustCenterFileResolver interface {
|
||||
IsUserAuthorized(ctx context.Context, obj *types.TrustCenterFile) (bool, error)
|
||||
@@ -805,6 +825,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
}
|
||||
|
||||
return e.ComplexityRoot.MailingListSubscriber.Email(childComplexity), true
|
||||
case "MailingListSubscriber.fullName":
|
||||
if e.ComplexityRoot.MailingListSubscriber.FullName == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.ComplexityRoot.MailingListSubscriber.FullName(childComplexity), true
|
||||
case "MailingListSubscriber.id":
|
||||
if e.ComplexityRoot.MailingListSubscriber.ID == nil {
|
||||
break
|
||||
@@ -824,6 +850,57 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
|
||||
return e.ComplexityRoot.MailingListSubscriber.UpdatedAt(childComplexity), true
|
||||
|
||||
case "MailingListUpdate.body":
|
||||
if e.ComplexityRoot.MailingListUpdate.Body == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.ComplexityRoot.MailingListUpdate.Body(childComplexity), true
|
||||
case "MailingListUpdate.id":
|
||||
if e.ComplexityRoot.MailingListUpdate.ID == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.ComplexityRoot.MailingListUpdate.ID(childComplexity), true
|
||||
case "MailingListUpdate.title":
|
||||
if e.ComplexityRoot.MailingListUpdate.Title == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.ComplexityRoot.MailingListUpdate.Title(childComplexity), true
|
||||
case "MailingListUpdate.updatedAt":
|
||||
if e.ComplexityRoot.MailingListUpdate.UpdatedAt == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.ComplexityRoot.MailingListUpdate.UpdatedAt(childComplexity), true
|
||||
|
||||
case "MailingListUpdateConnection.edges":
|
||||
if e.ComplexityRoot.MailingListUpdateConnection.Edges == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.ComplexityRoot.MailingListUpdateConnection.Edges(childComplexity), true
|
||||
case "MailingListUpdateConnection.pageInfo":
|
||||
if e.ComplexityRoot.MailingListUpdateConnection.PageInfo == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.ComplexityRoot.MailingListUpdateConnection.PageInfo(childComplexity), true
|
||||
|
||||
case "MailingListUpdateEdge.cursor":
|
||||
if e.ComplexityRoot.MailingListUpdateEdge.Cursor == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.ComplexityRoot.MailingListUpdateEdge.Cursor(childComplexity), true
|
||||
case "MailingListUpdateEdge.node":
|
||||
if e.ComplexityRoot.MailingListUpdateEdge.Node == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.ComplexityRoot.MailingListUpdateEdge.Node(childComplexity), true
|
||||
|
||||
case "Mutation.acceptElectronicSignature":
|
||||
if e.ComplexityRoot.Mutation.AcceptElectronicSignature == nil {
|
||||
break
|
||||
@@ -1258,6 +1335,17 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
}
|
||||
|
||||
return e.ComplexityRoot.TrustCenter.TrustCenterFiles(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey)), true
|
||||
case "TrustCenter.updates":
|
||||
if e.ComplexityRoot.TrustCenter.Updates == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_TrustCenter_updates_args(ctx, rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.ComplexityRoot.TrustCenter.Updates(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey)), true
|
||||
case "TrustCenter.vendors":
|
||||
if e.ComplexityRoot.TrustCenter.Vendors == nil {
|
||||
break
|
||||
@@ -1744,6 +1832,23 @@ type ComplianceFrameworkEdge
|
||||
node: ComplianceFramework!
|
||||
}
|
||||
|
||||
type MailingListUpdate implements Node @nda {
|
||||
id: ID!
|
||||
title: String!
|
||||
body: String!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
|
||||
type MailingListUpdateConnection @nda {
|
||||
edges: [MailingListUpdateEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type MailingListUpdateEdge @nda {
|
||||
cursor: CursorKey!
|
||||
node: MailingListUpdate!
|
||||
}
|
||||
|
||||
enum CountryCode
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.CountryCode") {
|
||||
AD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAD")
|
||||
@@ -2194,6 +2299,13 @@ type TrustCenter implements Node {
|
||||
last: Int
|
||||
before: CursorKey
|
||||
): ComplianceExternalURLConnection! @goField(forceResolver: true)
|
||||
|
||||
updates(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
): MailingListUpdateConnection! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type ComplianceExternalURL implements Node {
|
||||
@@ -2539,6 +2651,7 @@ type MailingListSubscriber implements Node
|
||||
model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.MailingListSubscriber"
|
||||
) {
|
||||
id: ID!
|
||||
fullName: String!
|
||||
email: EmailAddr!
|
||||
status: MailingListSubscriberStatus!
|
||||
createdAt: Datetime!
|
||||
@@ -2901,6 +3014,32 @@ func (ec *executionContext) field_TrustCenter_trustCenterFiles_args(ctx context.
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_TrustCenter_updates_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||
var err error
|
||||
args := map[string]any{}
|
||||
arg0, err := graphql.ProcessArgField(ctx, rawArgs, "first", ec.unmarshalOInt2ᚖint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args["first"] = arg0
|
||||
arg1, err := graphql.ProcessArgField(ctx, rawArgs, "after", ec.unmarshalOCursorKey2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋpageᚐCursorKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args["after"] = arg1
|
||||
arg2, err := graphql.ProcessArgField(ctx, rawArgs, "last", ec.unmarshalOInt2ᚖint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args["last"] = arg2
|
||||
arg3, err := graphql.ProcessArgField(ctx, rawArgs, "before", ec.unmarshalOCursorKey2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋpageᚐCursorKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args["before"] = arg3
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_TrustCenter_vendors_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||
var err error
|
||||
args := map[string]any{}
|
||||
@@ -4850,6 +4989,35 @@ func (ec *executionContext) fieldContext_MailingListSubscriber_id(_ context.Cont
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _MailingListSubscriber_fullName(ctx context.Context, field graphql.CollectedField, obj *types.MailingListSubscriber) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_MailingListSubscriber_fullName,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return obj.FullName, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNString2string,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_MailingListSubscriber_fullName(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "MailingListSubscriber",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type String does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _MailingListSubscriber_email(ctx context.Context, field graphql.CollectedField, obj *types.MailingListSubscriber) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
@@ -4966,6 +5134,290 @@ func (ec *executionContext) fieldContext_MailingListSubscriber_updatedAt(_ conte
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _MailingListUpdate_id(ctx context.Context, field graphql.CollectedField, obj *types.MailingListUpdate) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_MailingListUpdate_id,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return obj.ID, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_MailingListUpdate_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "MailingListUpdate",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type ID does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _MailingListUpdate_title(ctx context.Context, field graphql.CollectedField, obj *types.MailingListUpdate) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_MailingListUpdate_title,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return obj.Title, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNString2string,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_MailingListUpdate_title(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "MailingListUpdate",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type String does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _MailingListUpdate_body(ctx context.Context, field graphql.CollectedField, obj *types.MailingListUpdate) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_MailingListUpdate_body,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return obj.Body, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNString2string,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_MailingListUpdate_body(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "MailingListUpdate",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type String does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _MailingListUpdate_updatedAt(ctx context.Context, field graphql.CollectedField, obj *types.MailingListUpdate) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_MailingListUpdate_updatedAt,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return obj.UpdatedAt, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNDatetime2timeᚐTime,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_MailingListUpdate_updatedAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "MailingListUpdate",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type Datetime does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _MailingListUpdateConnection_edges(ctx context.Context, field graphql.CollectedField, obj *types.MailingListUpdateConnection) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_MailingListUpdateConnection_edges,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return obj.Edges, nil
|
||||
},
|
||||
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
|
||||
directive0 := next
|
||||
|
||||
directive1 := func(ctx context.Context) (any, error) {
|
||||
if ec.Directives.Nda == nil {
|
||||
var zeroVal []*types.MailingListUpdateEdge
|
||||
return zeroVal, errors.New("directive nda is not implemented")
|
||||
}
|
||||
return ec.Directives.Nda(ctx, obj, directive0)
|
||||
}
|
||||
|
||||
next = directive1
|
||||
return next
|
||||
},
|
||||
ec.marshalNMailingListUpdateEdge2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐMailingListUpdateEdgeᚄ,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_MailingListUpdateConnection_edges(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "MailingListUpdateConnection",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
switch field.Name {
|
||||
case "cursor":
|
||||
return ec.fieldContext_MailingListUpdateEdge_cursor(ctx, field)
|
||||
case "node":
|
||||
return ec.fieldContext_MailingListUpdateEdge_node(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type MailingListUpdateEdge", field.Name)
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _MailingListUpdateConnection_pageInfo(ctx context.Context, field graphql.CollectedField, obj *types.MailingListUpdateConnection) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_MailingListUpdateConnection_pageInfo,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return obj.PageInfo, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNPageInfo2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐPageInfo,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_MailingListUpdateConnection_pageInfo(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "MailingListUpdateConnection",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
switch field.Name {
|
||||
case "hasNextPage":
|
||||
return ec.fieldContext_PageInfo_hasNextPage(ctx, field)
|
||||
case "hasPreviousPage":
|
||||
return ec.fieldContext_PageInfo_hasPreviousPage(ctx, field)
|
||||
case "startCursor":
|
||||
return ec.fieldContext_PageInfo_startCursor(ctx, field)
|
||||
case "endCursor":
|
||||
return ec.fieldContext_PageInfo_endCursor(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type PageInfo", field.Name)
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _MailingListUpdateEdge_cursor(ctx context.Context, field graphql.CollectedField, obj *types.MailingListUpdateEdge) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_MailingListUpdateEdge_cursor,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return obj.Cursor, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNCursorKey2goᚗproboᚗincᚋproboᚋpkgᚋpageᚐCursorKey,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_MailingListUpdateEdge_cursor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "MailingListUpdateEdge",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type CursorKey does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _MailingListUpdateEdge_node(ctx context.Context, field graphql.CollectedField, obj *types.MailingListUpdateEdge) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_MailingListUpdateEdge_node,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return obj.Node, nil
|
||||
},
|
||||
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
|
||||
directive0 := next
|
||||
|
||||
directive1 := func(ctx context.Context) (any, error) {
|
||||
if ec.Directives.Nda == nil {
|
||||
var zeroVal *types.MailingListUpdate
|
||||
return zeroVal, errors.New("directive nda is not implemented")
|
||||
}
|
||||
return ec.Directives.Nda(ctx, obj, directive0)
|
||||
}
|
||||
|
||||
next = directive1
|
||||
return next
|
||||
},
|
||||
ec.marshalNMailingListUpdate2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐMailingListUpdate,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_MailingListUpdateEdge_node(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "MailingListUpdateEdge",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
switch field.Name {
|
||||
case "id":
|
||||
return ec.fieldContext_MailingListUpdate_id(ctx, field)
|
||||
case "title":
|
||||
return ec.fieldContext_MailingListUpdate_title(ctx, field)
|
||||
case "body":
|
||||
return ec.fieldContext_MailingListUpdate_body(ctx, field)
|
||||
case "updatedAt":
|
||||
return ec.fieldContext_MailingListUpdate_updatedAt(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type MailingListUpdate", field.Name)
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_sendMagicLink(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
@@ -6423,6 +6875,8 @@ func (ec *executionContext) fieldContext_Query_currentTrustCenter(_ context.Cont
|
||||
return ec.fieldContext_TrustCenter_complianceFrameworks(ctx, field)
|
||||
case "externalUrls":
|
||||
return ec.fieldContext_TrustCenter_externalUrls(ctx, field)
|
||||
case "updates":
|
||||
return ec.fieldContext_TrustCenter_updates(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type TrustCenter", field.Name)
|
||||
},
|
||||
@@ -6945,6 +7399,8 @@ func (ec *executionContext) fieldContext_SubscribeToMailingListPayload_subscript
|
||||
switch field.Name {
|
||||
case "id":
|
||||
return ec.fieldContext_MailingListSubscriber_id(ctx, field)
|
||||
case "fullName":
|
||||
return ec.fieldContext_MailingListSubscriber_fullName(ctx, field)
|
||||
case "email":
|
||||
return ec.fieldContext_MailingListSubscriber_email(ctx, field)
|
||||
case "status":
|
||||
@@ -7168,6 +7624,8 @@ func (ec *executionContext) fieldContext_TrustCenter_viewerSubscription(_ contex
|
||||
switch field.Name {
|
||||
case "id":
|
||||
return ec.fieldContext_MailingListSubscriber_id(ctx, field)
|
||||
case "fullName":
|
||||
return ec.fieldContext_MailingListSubscriber_fullName(ctx, field)
|
||||
case "email":
|
||||
return ec.fieldContext_MailingListSubscriber_email(ctx, field)
|
||||
case "status":
|
||||
@@ -7624,6 +8082,66 @@ func (ec *executionContext) fieldContext_TrustCenter_externalUrls(ctx context.Co
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _TrustCenter_updates(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_TrustCenter_updates,
|
||||
func(ctx context.Context) (any, error) {
|
||||
fc := graphql.GetFieldContext(ctx)
|
||||
return ec.Resolvers.TrustCenter().Updates(ctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey))
|
||||
},
|
||||
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
|
||||
directive0 := next
|
||||
|
||||
directive1 := func(ctx context.Context) (any, error) {
|
||||
if ec.Directives.Nda == nil {
|
||||
var zeroVal *types.MailingListUpdateConnection
|
||||
return zeroVal, errors.New("directive nda is not implemented")
|
||||
}
|
||||
return ec.Directives.Nda(ctx, obj, directive0)
|
||||
}
|
||||
|
||||
next = directive1
|
||||
return next
|
||||
},
|
||||
ec.marshalNMailingListUpdateConnection2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐMailingListUpdateConnection,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_TrustCenter_updates(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "TrustCenter",
|
||||
Field: field,
|
||||
IsMethod: true,
|
||||
IsResolver: true,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
switch field.Name {
|
||||
case "edges":
|
||||
return ec.fieldContext_MailingListUpdateConnection_edges(ctx, field)
|
||||
case "pageInfo":
|
||||
return ec.fieldContext_MailingListUpdateConnection_pageInfo(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type MailingListUpdateConnection", field.Name)
|
||||
},
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = ec.Recover(ctx, r)
|
||||
ec.Error(ctx, err)
|
||||
}
|
||||
}()
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
if fc.Args, err = ec.field_TrustCenter_updates_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return fc, err
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _TrustCenterAccess_id(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenterAccess) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
@@ -10701,6 +11219,13 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj
|
||||
return graphql.Null
|
||||
}
|
||||
return ec._Organization(ctx, sel, obj)
|
||||
case types.MailingListUpdate:
|
||||
return ec._MailingListUpdate(ctx, sel, &obj)
|
||||
case *types.MailingListUpdate:
|
||||
if obj == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
return ec._MailingListUpdate(ctx, sel, obj)
|
||||
case types.MailingListSubscriber:
|
||||
return ec._MailingListSubscriber(ctx, sel, &obj)
|
||||
case *types.MailingListSubscriber:
|
||||
@@ -11944,6 +12469,11 @@ func (ec *executionContext) _MailingListSubscriber(ctx context.Context, sel ast.
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "fullName":
|
||||
out.Values[i] = ec._MailingListSubscriber_fullName(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "email":
|
||||
out.Values[i] = ec._MailingListSubscriber_email(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
@@ -11987,6 +12517,148 @@ func (ec *executionContext) _MailingListSubscriber(ctx context.Context, sel ast.
|
||||
return out
|
||||
}
|
||||
|
||||
var mailingListUpdateImplementors = []string{"MailingListUpdate", "Node"}
|
||||
|
||||
func (ec *executionContext) _MailingListUpdate(ctx context.Context, sel ast.SelectionSet, obj *types.MailingListUpdate) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ec.OperationContext, sel, mailingListUpdateImplementors)
|
||||
|
||||
out := graphql.NewFieldSet(fields)
|
||||
deferred := make(map[string]*graphql.FieldSet)
|
||||
for i, field := range fields {
|
||||
switch field.Name {
|
||||
case "__typename":
|
||||
out.Values[i] = graphql.MarshalString("MailingListUpdate")
|
||||
case "id":
|
||||
out.Values[i] = ec._MailingListUpdate_id(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "title":
|
||||
out.Values[i] = ec._MailingListUpdate_title(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "body":
|
||||
out.Values[i] = ec._MailingListUpdate_body(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "updatedAt":
|
||||
out.Values[i] = ec._MailingListUpdate_updatedAt(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
}
|
||||
out.Dispatch(ctx)
|
||||
if out.Invalids > 0 {
|
||||
return graphql.Null
|
||||
}
|
||||
|
||||
atomic.AddInt32(&ec.Deferred, int32(len(deferred)))
|
||||
|
||||
for label, dfs := range deferred {
|
||||
ec.ProcessDeferredGroup(graphql.DeferredGroup{
|
||||
Label: label,
|
||||
Path: graphql.GetPath(ctx),
|
||||
FieldSet: dfs,
|
||||
Context: ctx,
|
||||
})
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
var mailingListUpdateConnectionImplementors = []string{"MailingListUpdateConnection"}
|
||||
|
||||
func (ec *executionContext) _MailingListUpdateConnection(ctx context.Context, sel ast.SelectionSet, obj *types.MailingListUpdateConnection) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ec.OperationContext, sel, mailingListUpdateConnectionImplementors)
|
||||
|
||||
out := graphql.NewFieldSet(fields)
|
||||
deferred := make(map[string]*graphql.FieldSet)
|
||||
for i, field := range fields {
|
||||
switch field.Name {
|
||||
case "__typename":
|
||||
out.Values[i] = graphql.MarshalString("MailingListUpdateConnection")
|
||||
case "edges":
|
||||
out.Values[i] = ec._MailingListUpdateConnection_edges(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "pageInfo":
|
||||
out.Values[i] = ec._MailingListUpdateConnection_pageInfo(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
}
|
||||
out.Dispatch(ctx)
|
||||
if out.Invalids > 0 {
|
||||
return graphql.Null
|
||||
}
|
||||
|
||||
atomic.AddInt32(&ec.Deferred, int32(len(deferred)))
|
||||
|
||||
for label, dfs := range deferred {
|
||||
ec.ProcessDeferredGroup(graphql.DeferredGroup{
|
||||
Label: label,
|
||||
Path: graphql.GetPath(ctx),
|
||||
FieldSet: dfs,
|
||||
Context: ctx,
|
||||
})
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
var mailingListUpdateEdgeImplementors = []string{"MailingListUpdateEdge"}
|
||||
|
||||
func (ec *executionContext) _MailingListUpdateEdge(ctx context.Context, sel ast.SelectionSet, obj *types.MailingListUpdateEdge) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ec.OperationContext, sel, mailingListUpdateEdgeImplementors)
|
||||
|
||||
out := graphql.NewFieldSet(fields)
|
||||
deferred := make(map[string]*graphql.FieldSet)
|
||||
for i, field := range fields {
|
||||
switch field.Name {
|
||||
case "__typename":
|
||||
out.Values[i] = graphql.MarshalString("MailingListUpdateEdge")
|
||||
case "cursor":
|
||||
out.Values[i] = ec._MailingListUpdateEdge_cursor(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "node":
|
||||
out.Values[i] = ec._MailingListUpdateEdge_node(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
}
|
||||
out.Dispatch(ctx)
|
||||
if out.Invalids > 0 {
|
||||
return graphql.Null
|
||||
}
|
||||
|
||||
atomic.AddInt32(&ec.Deferred, int32(len(deferred)))
|
||||
|
||||
for label, dfs := range deferred {
|
||||
ec.ProcessDeferredGroup(graphql.DeferredGroup{
|
||||
Label: label,
|
||||
Path: graphql.GetPath(ctx),
|
||||
FieldSet: dfs,
|
||||
Context: ctx,
|
||||
})
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
var mutationImplementors = []string{"Mutation"}
|
||||
|
||||
func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler {
|
||||
@@ -13285,6 +13957,42 @@ func (ec *executionContext) _TrustCenter(ctx context.Context, sel ast.SelectionS
|
||||
continue
|
||||
}
|
||||
|
||||
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
|
||||
case "updates":
|
||||
field := field
|
||||
|
||||
innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
}
|
||||
}()
|
||||
res = ec._TrustCenter_updates(ctx, field, obj)
|
||||
if res == graphql.Null {
|
||||
atomic.AddUint32(&fs.Invalids, 1)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
if field.Deferrable != nil {
|
||||
dfs, ok := deferred[field.Deferrable.Label]
|
||||
di := 0
|
||||
if ok {
|
||||
dfs.AddField(field)
|
||||
di = len(dfs.Values) - 1
|
||||
} else {
|
||||
dfs = graphql.NewFieldSet([]graphql.CollectedField{field})
|
||||
deferred[field.Deferrable.Label] = dfs
|
||||
}
|
||||
dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler {
|
||||
return innerFunc(ctx, dfs)
|
||||
})
|
||||
|
||||
// don't run the out.Concurrently() call below
|
||||
out.Values[i] = graphql.Null
|
||||
continue
|
||||
}
|
||||
|
||||
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
@@ -15532,6 +16240,56 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func (ec *executionContext) marshalNMailingListUpdate2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐMailingListUpdate(ctx context.Context, sel ast.SelectionSet, v *types.MailingListUpdate) graphql.Marshaler {
|
||||
if v == nil {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
return ec._MailingListUpdate(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNMailingListUpdateConnection2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐMailingListUpdateConnection(ctx context.Context, sel ast.SelectionSet, v types.MailingListUpdateConnection) graphql.Marshaler {
|
||||
return ec._MailingListUpdateConnection(ctx, sel, &v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNMailingListUpdateConnection2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐMailingListUpdateConnection(ctx context.Context, sel ast.SelectionSet, v *types.MailingListUpdateConnection) graphql.Marshaler {
|
||||
if v == nil {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
return ec._MailingListUpdateConnection(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNMailingListUpdateEdge2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐMailingListUpdateEdgeᚄ(ctx context.Context, sel ast.SelectionSet, v []*types.MailingListUpdateEdge) graphql.Marshaler {
|
||||
ret := graphql.MarshalSliceConcurrently(ctx, len(v), 0, false, func(ctx context.Context, i int) graphql.Marshaler {
|
||||
fc := graphql.GetFieldContext(ctx)
|
||||
fc.Result = &v[i]
|
||||
return ec.marshalNMailingListUpdateEdge2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐMailingListUpdateEdge(ctx, sel, v[i])
|
||||
})
|
||||
|
||||
for _, e := range ret {
|
||||
if e == graphql.Null {
|
||||
return graphql.Null
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNMailingListUpdateEdge2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐMailingListUpdateEdge(ctx context.Context, sel ast.SelectionSet, v *types.MailingListUpdateEdge) graphql.Marshaler {
|
||||
if v == nil {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
return ec._MailingListUpdateEdge(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNNode2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐNode(ctx context.Context, sel ast.SelectionSet, v types.Node) graphql.Marshaler {
|
||||
if v == nil {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
|
||||
type MailingListSubscriber struct {
|
||||
ID gid.GID `json:"id"`
|
||||
FullName string `json:"fullName"`
|
||||
Email mail.Addr `json:"email"`
|
||||
Status coredata.MailingListSubscriberStatus `json:"status"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
@@ -36,6 +37,7 @@ func (m MailingListSubscriber) GetID() gid.GID { return m.ID }
|
||||
func NewMailingListSubscriber(s *coredata.MailingListSubscriber) *MailingListSubscriber {
|
||||
return &MailingListSubscriber{
|
||||
ID: s.ID,
|
||||
FullName: s.FullName,
|
||||
Email: s.Email,
|
||||
Status: s.Status,
|
||||
CreatedAt: s.CreatedAt,
|
||||
|
||||
50
pkg/server/api/trust/v1/types/mailing_list_update.go
Normal file
50
pkg/server/api/trust/v1/types/mailing_list_update.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
func NewMailingListUpdate(mlu *coredata.MailingListUpdate) *MailingListUpdate {
|
||||
return &MailingListUpdate{
|
||||
ID: mlu.ID,
|
||||
Title: mlu.Title,
|
||||
Body: mlu.Body,
|
||||
UpdatedAt: mlu.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewMailingListUpdateEdge(mlu *coredata.MailingListUpdate) *MailingListUpdateEdge {
|
||||
return &MailingListUpdateEdge{
|
||||
Cursor: mlu.CursorKey(coredata.MailingListUpdateOrderFieldUpdatedAt),
|
||||
Node: NewMailingListUpdate(mlu),
|
||||
}
|
||||
}
|
||||
|
||||
func NewMailingListUpdateConnection(
|
||||
p *page.Page[*coredata.MailingListUpdate, coredata.MailingListUpdateOrderField],
|
||||
) *MailingListUpdateConnection {
|
||||
edges := make([]*MailingListUpdateEdge, len(p.Data))
|
||||
for i, mlu := range p.Data {
|
||||
edges[i] = NewMailingListUpdateEdge(mlu)
|
||||
}
|
||||
|
||||
return &MailingListUpdateConnection{
|
||||
Edges: edges,
|
||||
PageInfo: NewPageInfo(p),
|
||||
}
|
||||
}
|
||||
@@ -153,6 +153,26 @@ type Identity struct {
|
||||
func (Identity) IsNode() {}
|
||||
func (this Identity) GetID() gid.GID { return this.ID }
|
||||
|
||||
type MailingListUpdate struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (MailingListUpdate) IsNode() {}
|
||||
func (this MailingListUpdate) GetID() gid.GID { return this.ID }
|
||||
|
||||
type MailingListUpdateConnection struct {
|
||||
Edges []*MailingListUpdateEdge `json:"edges"`
|
||||
PageInfo *PageInfo `json:"pageInfo"`
|
||||
}
|
||||
|
||||
type MailingListUpdateEdge struct {
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *MailingListUpdate `json:"node"`
|
||||
}
|
||||
|
||||
type Mutation struct {
|
||||
}
|
||||
|
||||
@@ -261,6 +281,7 @@ type TrustCenter struct {
|
||||
TrustCenterFiles *TrustCenterFileConnection `json:"trustCenterFiles"`
|
||||
ComplianceFrameworks *ComplianceFrameworkConnection `json:"complianceFrameworks"`
|
||||
ExternalUrls *ComplianceExternalURLConnection `json:"externalUrls"`
|
||||
Updates *MailingListUpdateConnection `json:"updates"`
|
||||
}
|
||||
|
||||
func (TrustCenter) IsNode() {}
|
||||
|
||||
@@ -704,18 +704,18 @@ func (r *mutationResolver) SubscribeToMailingList(ctx context.Context) (*types.S
|
||||
|
||||
identity := authn.IdentityFromContext(ctx)
|
||||
|
||||
subscriber, err := r.mailman.CreateSubscriber(ctx, *trustCenter.MailingListID, identity.EmailAddress, identity.FullName)
|
||||
subscriber, err := r.mailman.CreateSubscriber(
|
||||
ctx,
|
||||
*trustCenter.MailingListID,
|
||||
identity.EmailAddress,
|
||||
identity.FullName,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, mailman.ErrSubscriberAlreadyExist) {
|
||||
subscriber, err = r.mailman.GetSubscriber(ctx, *trustCenter.MailingListID, identity.EmailAddress)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot get existing mailing list subscription", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
} else {
|
||||
r.logger.ErrorCtx(ctx, "cannot subscribe to mailing list", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
return nil, gqlutils.Conflictf(ctx, "already subscribed to this mailing list")
|
||||
}
|
||||
r.logger.ErrorCtx(ctx, "cannot subscribe to mailing list", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.SubscribeToMailingListPayload{
|
||||
@@ -738,10 +738,13 @@ func (r *mutationResolver) UnsubscribeFromMailingList(ctx context.Context) (*typ
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
if subscriber == nil {
|
||||
return nil, gqlutils.NotFoundf(ctx, "mailing list subscription not found")
|
||||
return nil, gqlutils.NotFoundf(ctx, "not subscribed to this mailing list")
|
||||
}
|
||||
|
||||
if err := r.mailman.DeleteSubscriber(ctx, subscriber.ID); err != nil {
|
||||
if errors.Is(err, mailman.ErrSubscriberNotFound) {
|
||||
return nil, gqlutils.NotFoundf(ctx, "not subscribed to this mailing list")
|
||||
}
|
||||
r.logger.ErrorCtx(ctx, "cannot unsubscribe from mailing list", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
@@ -1207,6 +1210,35 @@ func (r *trustCenterResolver) ExternalUrls(ctx context.Context, obj *types.Trust
|
||||
return types.NewComplianceExternalURLConnection(result), nil
|
||||
}
|
||||
|
||||
// Updates is the resolver for the updates field.
|
||||
func (r *trustCenterResolver) Updates(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.MailingListUpdateConnection, error) {
|
||||
trustService := r.TrustService(ctx, obj.ID.TenantID())
|
||||
|
||||
tc, err := trustService.TrustCenters.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot load trust center", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
if tc.MailingListID == nil {
|
||||
return &types.MailingListUpdateConnection{Edges: []*types.MailingListUpdateEdge{}, PageInfo: &types.PageInfo{}}, nil
|
||||
}
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.MailingListUpdateOrderField]{
|
||||
Field: coredata.MailingListUpdateOrderFieldUpdatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
result, err := r.mailman.ListSentMailingListUpdates(ctx, *tc.MailingListID, cursor)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot list mailing list updates", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewMailingListUpdateConnection(result), nil
|
||||
}
|
||||
|
||||
// IsUserAuthorized is the resolver for the isUserAuthorized field.
|
||||
func (r *trustCenterFileResolver) IsUserAuthorized(ctx context.Context, obj *types.TrustCenterFile) (bool, error) {
|
||||
trustService := r.TrustService(ctx, obj.ID.TenantID())
|
||||
|
||||
124
pkg/server/mailactions/confirm.go
Normal file
124
pkg/server/mailactions/confirm.go
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package mailactions
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"go.probo.inc/probo/pkg/mailman"
|
||||
)
|
||||
|
||||
func confirmGetHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.URL.Query().Get("token")
|
||||
if token == "" {
|
||||
renderPage(
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
page{
|
||||
Title: "Invalid link",
|
||||
Heading: "Invalid link",
|
||||
Body: "This confirmation link is missing required information. Please use the link from your email.",
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
renderPage(
|
||||
w,
|
||||
http.StatusOK,
|
||||
page{
|
||||
Title: "Confirm subscription",
|
||||
Heading: "Confirm your subscription",
|
||||
Body: "Click the button below to confirm that you want to receive updates.",
|
||||
Form: &form{
|
||||
ActionURL: template.URL("?token=" + url.QueryEscape(token)),
|
||||
Button: "Confirm subscription",
|
||||
Danger: false,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func confirmPostHandler(mailmanSvc *mailman.Service, tokenSecret string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.URL.Query().Get("token")
|
||||
if token == "" {
|
||||
renderPage(
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
page{
|
||||
Title: "Invalid link",
|
||||
Heading: "Invalid link",
|
||||
Body: "This confirmation link is missing required information. Please use the link from your email.",
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := mailman.ValidateConfirmToken(tokenSecret, token)
|
||||
if err != nil {
|
||||
renderPage(
|
||||
w,
|
||||
http.StatusUnauthorized,
|
||||
page{
|
||||
Title: "Invalid link",
|
||||
Heading: "Invalid or expired link",
|
||||
Body: "This confirmation link is invalid or has expired. Confirmation links are valid for 30 days — please re-subscribe to get a new one.",
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if err := mailmanSvc.ConfirmSubscriberByEmail(r.Context(), data.MailingListID, data.Email); err != nil {
|
||||
if errors.Is(err, mailman.ErrSubscriberNotFound) {
|
||||
renderPage(
|
||||
w,
|
||||
http.StatusNotFound, page{
|
||||
Title: "Not found",
|
||||
Heading: "Subscription not found",
|
||||
Body: "We could not find your subscription. It may have already been cancelled or this link was already used.",
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
renderPage(
|
||||
w,
|
||||
http.StatusInternalServerError,
|
||||
page{
|
||||
Title: "Something went wrong",
|
||||
Heading: "Something went wrong",
|
||||
Body: "We could not confirm your subscription. Please try again later.",
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
renderPage(
|
||||
w,
|
||||
http.StatusOK,
|
||||
page{
|
||||
Title: "Subscription confirmed",
|
||||
Heading: "Subscription confirmed",
|
||||
Body: "You're now subscribed and will receive updates.",
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
40
pkg/server/mailactions/mux.go
Normal file
40
pkg/server/mailactions/mux.go
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// Package mailactions provides HTTP handlers for mailing list subscription
|
||||
// management. All routes are mounted at /mail-actions/ with no API version
|
||||
// prefix so they can be linked directly from emails and bookmarked by users.
|
||||
//
|
||||
// GET /mail-actions/unsubscribe shows an unsubscribe confirmation page
|
||||
// POST /mail-actions/unsubscribe RFC 8058 one-click unsubscribe
|
||||
// GET /mail-actions/confirm shows a subscription confirmation page
|
||||
// POST /mail-actions/confirm confirms a pending subscription
|
||||
package mailactions
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.probo.inc/probo/pkg/mailman"
|
||||
)
|
||||
|
||||
func NewMux(mailmanSvc *mailman.Service, tokenSecret string) *chi.Mux {
|
||||
r := chi.NewMux()
|
||||
|
||||
r.Get("/unsubscribe", unsubscribeGetHandler())
|
||||
r.Post("/unsubscribe", unsubscribePostHandler(mailmanSvc, tokenSecret))
|
||||
|
||||
r.Get("/confirm", confirmGetHandler())
|
||||
r.Post("/confirm", confirmPostHandler(mailmanSvc, tokenSecret))
|
||||
|
||||
return r
|
||||
}
|
||||
45
pkg/server/mailactions/render.go
Normal file
45
pkg/server/mailactions/render.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package mailactions
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"html/template"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//go:embed templates/page.html.tmpl
|
||||
var pageTmplHTML string
|
||||
|
||||
type form struct {
|
||||
ActionURL template.URL
|
||||
Button string
|
||||
Danger bool
|
||||
}
|
||||
|
||||
type page struct {
|
||||
Title string
|
||||
Heading string
|
||||
Body string
|
||||
Form *form
|
||||
}
|
||||
|
||||
var tmpl = template.Must(template.New("page.html.tmpl").Parse(pageTmplHTML))
|
||||
|
||||
func renderPage(w http.ResponseWriter, status int, p page) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(status)
|
||||
_ = tmpl.Execute(w, p)
|
||||
}
|
||||
99
pkg/server/mailactions/templates/page.html.tmpl
Normal file
99
pkg/server/mailactions/templates/page.html.tmpl
Normal file
@@ -0,0 +1,99 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{{.Title}}</title>
|
||||
<style>
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
background: #f3f4f6;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1.5rem;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
padding: 2.5rem 2rem;
|
||||
max-width: 26rem;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 0.625rem;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 0.9375rem;
|
||||
color: #6b7280;
|
||||
line-height: 1.6;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
form {
|
||||
margin-top: 1.75rem;
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: inherit;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
padding: 0.625rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.primary {
|
||||
background: #111827;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.primary:hover {
|
||||
background: #1f2937;
|
||||
}
|
||||
|
||||
.danger {
|
||||
background: #dc2626;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.danger:hover {
|
||||
background: #b91c1c;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>{{.Heading}}</h1>
|
||||
<p>{{.Body}}</p>
|
||||
{{- if .Form}}
|
||||
<form method="POST" action="{{.Form.ActionURL}}">
|
||||
<button type="submit" class="{{if .Form.Danger}}danger{{else}}primary{{end}}">
|
||||
{{.Form.Button}}
|
||||
</button>
|
||||
</form>
|
||||
{{- end}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
116
pkg/server/mailactions/unsubscribe.go
Normal file
116
pkg/server/mailactions/unsubscribe.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package mailactions
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"go.probo.inc/probo/pkg/mailman"
|
||||
)
|
||||
|
||||
func unsubscribeGetHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.URL.Query().Get("token")
|
||||
if token == "" {
|
||||
renderPage(
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
page{
|
||||
Title: "Invalid link",
|
||||
Heading: "Invalid link",
|
||||
Body: "This unsubscribe link is missing required information. Please use the link from your email.",
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
renderPage(
|
||||
w,
|
||||
http.StatusOK,
|
||||
page{
|
||||
Title: "Unsubscribe",
|
||||
Heading: "Unsubscribe from mailing list",
|
||||
Body: "Click the button below to confirm that you no longer want to receive updates.",
|
||||
Form: &form{
|
||||
ActionURL: template.URL("?token=" + url.QueryEscape(token)),
|
||||
Button: "Confirm unsubscribe",
|
||||
Danger: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func unsubscribePostHandler(mailmanSvc *mailman.Service, tokenSecret string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.URL.Query().Get("token")
|
||||
if token == "" {
|
||||
renderPage(
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
page{
|
||||
Title: "Invalid link",
|
||||
Heading: "Invalid link",
|
||||
Body: "This unsubscribe link is missing required information. Please use the link from your email.",
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := mailman.ValidateUnsubscribeToken(tokenSecret, token)
|
||||
if err != nil {
|
||||
renderPage(
|
||||
w,
|
||||
http.StatusUnauthorized,
|
||||
page{
|
||||
Title: "Invalid link",
|
||||
Heading: "Invalid or expired link",
|
||||
Body: "This unsubscribe link is invalid or has expired.",
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if err := mailmanSvc.UnsubscribeByEmail(r.Context(), data.MailingListID, data.Email); err != nil {
|
||||
if !errors.Is(err, mailman.ErrSubscriberNotFound) {
|
||||
renderPage(
|
||||
w,
|
||||
http.StatusInternalServerError,
|
||||
page{
|
||||
Title: "Something went wrong",
|
||||
Heading: "Something went wrong",
|
||||
Body: "We could not process your request. Please try again later.",
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Also success when already unsubscribed — unsubscribe is idempotent
|
||||
// per RFC 8058.
|
||||
renderPage(
|
||||
w,
|
||||
http.StatusOK,
|
||||
page{
|
||||
Title: "Unsubscribed",
|
||||
Heading: "You've been unsubscribed",
|
||||
Body: "You will no longer receive updates.",
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"go.probo.inc/probo/pkg/securecookie"
|
||||
"go.probo.inc/probo/pkg/server/api"
|
||||
"go.probo.inc/probo/pkg/server/api/compliancepage"
|
||||
"go.probo.inc/probo/pkg/server/mailactions"
|
||||
trust_web "go.probo.inc/probo/pkg/server/trust"
|
||||
console_web "go.probo.inc/probo/pkg/server/web"
|
||||
"go.probo.inc/probo/pkg/slack"
|
||||
@@ -57,14 +58,15 @@ type Config struct {
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
apiServer *api.Server
|
||||
consoleWebServer *console_web.Server
|
||||
trustWebServer *trust_web.Server
|
||||
router *chi.Mux
|
||||
extraHeaderFields map[string]string
|
||||
proboService *probo.Service
|
||||
trustService *trust.Service
|
||||
logger *log.Logger
|
||||
apiServer *api.Server
|
||||
mailActionsHandler http.Handler
|
||||
consoleWebServer *console_web.Server
|
||||
trustWebServer *trust_web.Server
|
||||
router *chi.Mux
|
||||
extraHeaderFields map[string]string
|
||||
proboService *probo.Service
|
||||
trustService *trust.Service
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func NewServer(cfg Config) (*Server, error) {
|
||||
@@ -102,14 +104,15 @@ func NewServer(cfg Config) (*Server, error) {
|
||||
router := chi.NewRouter()
|
||||
|
||||
server := &Server{
|
||||
apiServer: apiServer,
|
||||
consoleWebServer: consoleWebServer,
|
||||
trustWebServer: trustWebServer,
|
||||
router: router,
|
||||
extraHeaderFields: cfg.ExtraHeaderFields,
|
||||
proboService: cfg.Probo,
|
||||
trustService: cfg.Trust,
|
||||
logger: cfg.Logger,
|
||||
apiServer: apiServer,
|
||||
mailActionsHandler: mailactions.NewMux(cfg.Mailman, cfg.TokenSecret),
|
||||
consoleWebServer: consoleWebServer,
|
||||
trustWebServer: trustWebServer,
|
||||
router: router,
|
||||
extraHeaderFields: cfg.ExtraHeaderFields,
|
||||
proboService: cfg.Probo,
|
||||
trustService: cfg.Trust,
|
||||
logger: cfg.Logger,
|
||||
}
|
||||
|
||||
server.setupRoutes(cfg.BaseURL.String())
|
||||
@@ -119,6 +122,7 @@ func NewServer(cfg Config) (*Server, error) {
|
||||
|
||||
func (s *Server) setupRoutes(baseURL string) {
|
||||
s.router.Mount("/api", http.StripPrefix("/api", s.apiServer))
|
||||
s.router.Mount("/mail-actions", http.StripPrefix("/mail-actions", s.mailActionsHandler))
|
||||
|
||||
s.router.Route("/trust/{slugOrId}", func(r chi.Router) {
|
||||
r.Use(compliancepage.NewIDMiddleware(s.trustService, baseURL))
|
||||
|
||||
Reference in New Issue
Block a user