Add validation to mailman service
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<160a29eaaba4b3db2c1b7aca34164dd7>>
|
||||
* @generated SignedSource<<44dade2df63778a95d78ef9aaf6c8fa2>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -11,9 +11,9 @@
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type MailingListUpdateStatus = "DRAFT" | "ENQUEUED" | "PROCESSING" | "SENT";
|
||||
export type UpdateMailingListUpdateInput = {
|
||||
body: string;
|
||||
body?: string | null | undefined;
|
||||
id: string;
|
||||
title: string;
|
||||
title?: string | null | undefined;
|
||||
};
|
||||
export type ComplianceUpdateFormDialogUpdateMutation$variables = {
|
||||
input: UpdateMailingListUpdateInput;
|
||||
|
||||
@@ -30,6 +30,13 @@ import (
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/mail"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/validator"
|
||||
)
|
||||
|
||||
const (
|
||||
updateTitleMaxLength = 200
|
||||
updateBodyMaxLength = 50000
|
||||
subscriberFullNameMaxLength = 200
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -51,6 +58,56 @@ func NewService(pgClient *pg.Client, fm *filemanager.Service, tokenSecret string
|
||||
return &Service{pg: pgClient, fm: fm, tokenSecret: tokenSecret, apiBaseURL: apiBaseURL, bucket: bucket, encryptionKey: encryptionKey, logger: logger}
|
||||
}
|
||||
|
||||
type (
|
||||
CreateMailingListUpdateRequest struct {
|
||||
MailingListID gid.GID
|
||||
Title string
|
||||
Body string
|
||||
}
|
||||
|
||||
UpdateMailingListUpdateRequest struct {
|
||||
ID gid.GID
|
||||
Title *string
|
||||
Body *string
|
||||
}
|
||||
|
||||
CreateSubscriberRequest struct {
|
||||
MailingListID gid.GID
|
||||
Email mail.Addr
|
||||
FullName string
|
||||
}
|
||||
)
|
||||
|
||||
func (r *CreateMailingListUpdateRequest) Validate() error {
|
||||
v := validator.New()
|
||||
|
||||
v.Check(r.MailingListID, "mailing_list_id", validator.Required(), validator.GID(coredata.MailingListEntityType))
|
||||
v.Check(r.Title, "title", validator.Required(), validator.SafeText(updateTitleMaxLength))
|
||||
v.Check(r.Body, "body", validator.Required(), validator.SafeText(updateBodyMaxLength))
|
||||
|
||||
return v.Error()
|
||||
}
|
||||
|
||||
func (r *UpdateMailingListUpdateRequest) Validate() error {
|
||||
v := validator.New()
|
||||
|
||||
v.Check(r.ID, "id", validator.Required(), validator.GID(coredata.MailingListUpdateEntityType))
|
||||
v.Check(r.Title, "title", validator.SafeText(updateTitleMaxLength))
|
||||
v.Check(r.Body, "body", validator.SafeText(updateBodyMaxLength))
|
||||
|
||||
return v.Error()
|
||||
}
|
||||
|
||||
func (r *CreateSubscriberRequest) Validate() error {
|
||||
v := validator.New()
|
||||
|
||||
v.Check(r.MailingListID, "mailing_list_id", validator.Required(), validator.GID(coredata.MailingListEntityType))
|
||||
v.Check(r.Email, "email", validator.Required(), validator.NotEmpty())
|
||||
v.Check(r.FullName, "full_name", validator.Required(), validator.SafeText(subscriberFullNameMaxLength))
|
||||
|
||||
return v.Error()
|
||||
}
|
||||
|
||||
func (s *Service) UpdateMailingList(
|
||||
ctx context.Context,
|
||||
id gid.GID,
|
||||
@@ -116,10 +173,16 @@ func (s *Service) GetSubscriber(
|
||||
|
||||
func (s *Service) CreateSubscriber(
|
||||
ctx context.Context,
|
||||
mailingListID gid.GID,
|
||||
email mail.Addr,
|
||||
fullName string,
|
||||
req *CreateSubscriberRequest,
|
||||
) (*coredata.MailingListSubscriber, error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid request: %w", err)
|
||||
}
|
||||
|
||||
mailingListID := req.MailingListID
|
||||
email := req.Email
|
||||
fullName := req.FullName
|
||||
|
||||
scope := coredata.NewScopeFromObjectID(mailingListID)
|
||||
emailRecord, err := s.buildConfirmationMail(ctx, mailingListID, email, fullName)
|
||||
if err != nil {
|
||||
@@ -334,18 +397,21 @@ func (s *Service) ListSubscribers(
|
||||
|
||||
func (s *Service) CreateMailingListUpdate(
|
||||
ctx context.Context,
|
||||
mailingListID gid.GID,
|
||||
title string,
|
||||
body string,
|
||||
req *CreateMailingListUpdateRequest,
|
||||
) (*coredata.MailingListUpdate, error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid request: %w", err)
|
||||
}
|
||||
|
||||
mailingListID := req.MailingListID
|
||||
scope := coredata.NewScopeFromObjectID(mailingListID)
|
||||
now := time.Now()
|
||||
|
||||
mlu := &coredata.MailingListUpdate{
|
||||
ID: gid.New(scope.GetTenantID(), coredata.MailingListUpdateEntityType),
|
||||
MailingListID: mailingListID,
|
||||
Title: title,
|
||||
Body: body,
|
||||
Title: req.Title,
|
||||
Body: req.Body,
|
||||
Status: coredata.MailingListUpdateStatusDraft,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
@@ -406,17 +472,19 @@ func (s *Service) GetMailingListUpdate(
|
||||
|
||||
func (s *Service) UpdateMailingListUpdate(
|
||||
ctx context.Context,
|
||||
id gid.GID,
|
||||
title string,
|
||||
body string,
|
||||
req *UpdateMailingListUpdateRequest,
|
||||
) (*coredata.MailingListUpdate, error) {
|
||||
scope := coredata.NewScopeFromObjectID(id)
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid request: %w", err)
|
||||
}
|
||||
|
||||
scope := coredata.NewScopeFromObjectID(req.ID)
|
||||
var mlu coredata.MailingListUpdate
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := mlu.LoadByID(ctx, conn, scope, id); err != nil {
|
||||
if err := mlu.LoadByID(ctx, conn, scope, req.ID); err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
return ErrMailingListUpdateNotFound
|
||||
}
|
||||
@@ -427,8 +495,12 @@ func (s *Service) UpdateMailingListUpdate(
|
||||
return ErrMailingListUpdateAlreadySent
|
||||
}
|
||||
|
||||
mlu.Title = title
|
||||
mlu.Body = body
|
||||
if req.Title != nil {
|
||||
mlu.Title = *req.Title
|
||||
}
|
||||
if req.Body != nil {
|
||||
mlu.Body = *req.Body
|
||||
}
|
||||
mlu.UpdatedAt = time.Now()
|
||||
|
||||
if err := mlu.Update(ctx, conn, scope); err != nil {
|
||||
|
||||
@@ -3902,8 +3902,8 @@ input CreateMailingListUpdateInput {
|
||||
|
||||
input UpdateMailingListUpdateInput {
|
||||
id: ID!
|
||||
title: String!
|
||||
body: String!
|
||||
title: String
|
||||
body: String
|
||||
}
|
||||
|
||||
input SendMailingListUpdateInput {
|
||||
|
||||
@@ -15318,8 +15318,8 @@ input CreateMailingListUpdateInput {
|
||||
|
||||
input UpdateMailingListUpdateInput {
|
||||
id: ID!
|
||||
title: String!
|
||||
body: String!
|
||||
title: String
|
||||
body: String
|
||||
}
|
||||
|
||||
input SendMailingListUpdateInput {
|
||||
@@ -73536,14 +73536,14 @@ func (ec *executionContext) unmarshalInputUpdateMailingListUpdateInput(ctx conte
|
||||
it.ID = data
|
||||
case "title":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("title"))
|
||||
data, err := ec.unmarshalNString2string(ctx, v)
|
||||
data, err := ec.unmarshalOString2ᚖstring(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
it.Title = data
|
||||
case "body":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("body"))
|
||||
data, err := ec.unmarshalNString2string(ctx, v)
|
||||
data, err := ec.unmarshalOString2ᚖstring(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
|
||||
@@ -2227,8 +2227,8 @@ type UpdateMailingListPayload struct {
|
||||
|
||||
type UpdateMailingListUpdateInput struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
Title *string `json:"title,omitempty"`
|
||||
Body *string `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateMailingListUpdatePayload struct {
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"go.probo.inc/probo/pkg/server/api/console/v1/types"
|
||||
"go.probo.inc/probo/pkg/server/gqlutils"
|
||||
"go.probo.inc/probo/pkg/server/gqlutils/types/cursor"
|
||||
"go.probo.inc/probo/pkg/validator"
|
||||
)
|
||||
|
||||
// StateOfApplicability is the resolver for the stateOfApplicability field.
|
||||
@@ -2072,8 +2073,18 @@ func (r *mutationResolver) CreateMailingListUpdate(ctx context.Context, input ty
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mlu, err := r.mailman.CreateMailingListUpdate(ctx, input.MailingListID, input.Title, input.Body)
|
||||
mlu, err := r.mailman.CreateMailingListUpdate(
|
||||
ctx,
|
||||
&mailman.CreateMailingListUpdateRequest{
|
||||
MailingListID: input.MailingListID,
|
||||
Title: input.Title,
|
||||
Body: input.Body,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
||||
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
|
||||
}
|
||||
r.logger.ErrorCtx(ctx, "cannot create mailing list update", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
@@ -2089,8 +2100,18 @@ func (r *mutationResolver) UpdateMailingListUpdate(ctx context.Context, input ty
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mlu, err := r.mailman.UpdateMailingListUpdate(ctx, input.ID, input.Title, input.Body)
|
||||
mlu, err := r.mailman.UpdateMailingListUpdate(
|
||||
ctx,
|
||||
&mailman.UpdateMailingListUpdateRequest{
|
||||
ID: input.ID,
|
||||
Title: input.Title,
|
||||
Body: input.Body,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
||||
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
|
||||
}
|
||||
if errors.Is(err, mailman.ErrMailingListUpdateAlreadySent) {
|
||||
return nil, gqlutils.Conflictf(ctx, "mailing list update can only be edited when in draft")
|
||||
}
|
||||
@@ -2173,11 +2194,16 @@ func (r *mutationResolver) CreateMailingListSubscriber(ctx context.Context, inpu
|
||||
|
||||
subscriber, err := r.mailman.CreateSubscriber(
|
||||
ctx,
|
||||
input.MailingListID,
|
||||
input.Email,
|
||||
input.FullName,
|
||||
&mailman.CreateSubscriberRequest{
|
||||
MailingListID: input.MailingListID,
|
||||
Email: input.Email,
|
||||
FullName: input.FullName,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
||||
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
|
||||
}
|
||||
if errors.Is(err, mailman.ErrSubscriberAlreadyExist) {
|
||||
return nil, gqlutils.Conflictf(ctx, "subscriber already exists in this mailing list")
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"go.probo.inc/probo/pkg/server/api/trust/v1/types"
|
||||
"go.probo.inc/probo/pkg/server/gqlutils"
|
||||
"go.probo.inc/probo/pkg/trust"
|
||||
"go.probo.inc/probo/pkg/validator"
|
||||
)
|
||||
|
||||
// Framework is the resolver for the framework field.
|
||||
@@ -706,11 +707,16 @@ func (r *mutationResolver) SubscribeToMailingList(ctx context.Context) (*types.S
|
||||
|
||||
subscriber, err := r.mailman.CreateSubscriber(
|
||||
ctx,
|
||||
*trustCenter.MailingListID,
|
||||
identity.EmailAddress,
|
||||
identity.FullName,
|
||||
&mailman.CreateSubscriberRequest{
|
||||
MailingListID: *trustCenter.MailingListID,
|
||||
Email: identity.EmailAddress,
|
||||
FullName: identity.FullName,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
||||
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
|
||||
}
|
||||
if errors.Is(err, mailman.ErrSubscriberAlreadyExist) {
|
||||
return nil, gqlutils.Conflictf(ctx, "already subscribed to this mailing list")
|
||||
}
|
||||
|
||||
@@ -170,6 +170,14 @@ func Invalidf(ctx context.Context, format string, a ...any) *gqlerror.Error {
|
||||
return Invalid(ctx, fmt.Errorf(format, a...))
|
||||
}
|
||||
|
||||
func InvalidValidationErrors(ctx context.Context, errs validator.ValidationErrors) gqlerror.List {
|
||||
gqlErrors := make(gqlerror.List, 0, len(errs))
|
||||
for _, ve := range errs {
|
||||
gqlErrors = append(gqlErrors, Invalid(ctx, ve))
|
||||
}
|
||||
return gqlErrors
|
||||
}
|
||||
|
||||
func Internal(ctx context.Context) *gqlerror.Error {
|
||||
return &gqlerror.Error{
|
||||
Message: "An internal server error occurred. Please try again later.",
|
||||
|
||||
Reference in New Issue
Block a user