todosrht-lmtp: Use GraphQL for comment submission
Use GraphQL to submit comments from incoming emails so that GraphQL-native webhooks are delivered. Also use comments to document internal GraphQL mutations instead of docstrings. References: https://todo.sr.ht/~sircmpwn/sr.ht/315
This commit is contained in:
parent
75a118a77d
commit
10d9d29edb
|
@ -735,14 +735,31 @@ input SubmitTicketInput {
|
||||||
externalUrl: String
|
externalUrl: String
|
||||||
}
|
}
|
||||||
|
|
||||||
"For internal use only."
|
# For internal use only.
|
||||||
input SubmitEmailInput {
|
input SubmitTicketEmailInput {
|
||||||
subject: String!
|
subject: String!
|
||||||
body: String
|
body: String
|
||||||
senderId: Int!
|
senderId: Int!
|
||||||
messageId: String!
|
messageId: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# For internal use only.
|
||||||
|
enum EmailCmd {
|
||||||
|
RESOLVE
|
||||||
|
REOPEN
|
||||||
|
LABEL
|
||||||
|
UNLABEL
|
||||||
|
}
|
||||||
|
|
||||||
|
# For internal use only.
|
||||||
|
input SubmitCommentEmailInput {
|
||||||
|
text: String!
|
||||||
|
senderId: Int!
|
||||||
|
cmd: EmailCmd
|
||||||
|
resolution: TicketResolution
|
||||||
|
labelIds: [Int!]
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
You may omit any fields to leave them unchanged. To remove the ticket body,
|
You may omit any fields to leave them unchanged. To remove the ticket body,
|
||||||
set it to null.
|
set it to null.
|
||||||
|
@ -887,9 +904,13 @@ type Mutation {
|
||||||
submitTicket(trackerId: Int!,
|
submitTicket(trackerId: Int!,
|
||||||
input: SubmitTicketInput!): Ticket! @access(scope: TICKETS, kind: RW)
|
input: SubmitTicketInput!): Ticket! @access(scope: TICKETS, kind: RW)
|
||||||
|
|
||||||
"Creates a new ticket from an incoming email. (For internal use only)"
|
# Creates a new ticket from an incoming email. (For internal use only)
|
||||||
submitEmail(trackerId: Int!,
|
submitTicketEmail(trackerId: Int!,
|
||||||
input: SubmitEmailInput!): Ticket! @internal
|
input: SubmitTicketEmailInput!): Ticket! @internal
|
||||||
|
|
||||||
|
# Creates a new comment from an incoming email. (For internal use only)
|
||||||
|
submitCommentEmail(trackerId: Int!, ticketId: Int!,
|
||||||
|
input: SubmitCommentEmailInput!): Event! @internal
|
||||||
|
|
||||||
"Updates a ticket's subject or body"
|
"Updates a ticket's subject or body"
|
||||||
updateTicket(trackerId: Int!, ticketId: Int!,
|
updateTicket(trackerId: Int!, ticketId: Int!,
|
||||||
|
|
|
@ -835,7 +835,7 @@ func (r *mutationResolver) SubmitTicket(ctx context.Context, trackerID int, inpu
|
||||||
return &ticket, nil
|
return &ticket, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) SubmitEmail(ctx context.Context, trackerID int, input model.SubmitEmailInput) (*model.Ticket, error) {
|
func (r *mutationResolver) SubmitTicketEmail(ctx context.Context, trackerID int, input model.SubmitTicketEmailInput) (*model.Ticket, error) {
|
||||||
validation := valid.New(ctx)
|
validation := valid.New(ctx)
|
||||||
validation.Expect(len(input.Subject) <= 2048,
|
validation.Expect(len(input.Subject) <= 2048,
|
||||||
"Ticket subject must be fewer than to 2049 characters.").
|
"Ticket subject must be fewer than to 2049 characters.").
|
||||||
|
@ -946,6 +946,244 @@ func (r *mutationResolver) SubmitEmail(ctx context.Context, trackerID int, input
|
||||||
return &ticket, nil
|
return &ticket, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) SubmitCommentEmail(ctx context.Context, trackerID int, ticketID int, input model.SubmitCommentEmailInput) (*model.Event, error) {
|
||||||
|
tracker, err := loaders.ForContext(ctx).TrackersByID.Load(trackerID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if tracker == nil {
|
||||||
|
return nil, fmt.Errorf("No tracker by ID %d found for this user", trackerID)
|
||||||
|
}
|
||||||
|
if !tracker.CanComment() {
|
||||||
|
return nil, fmt.Errorf("Access denied")
|
||||||
|
}
|
||||||
|
|
||||||
|
ticket, err := loaders.ForContext(ctx).
|
||||||
|
TicketsByTrackerID.Load([2]int{trackerID, ticketID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if ticket == nil {
|
||||||
|
return nil, errors.New("No such ticket")
|
||||||
|
}
|
||||||
|
|
||||||
|
owner, err := loaders.ForContext(ctx).UsersByID.Load(tracker.OwnerID)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTicket := sq.Update("ticket").
|
||||||
|
PlaceholderFormat(sq.Dollar)
|
||||||
|
insertEvent := sq.Insert("event").
|
||||||
|
PlaceholderFormat(sq.Dollar).
|
||||||
|
Columns("created", "event_type",
|
||||||
|
"ticket_id", "participant_id", "comment_id",
|
||||||
|
"old_status", "new_status", "old_resolution", "new_resolution")
|
||||||
|
|
||||||
|
var (
|
||||||
|
oldStatus *int
|
||||||
|
_oldStatus int
|
||||||
|
newStatus *int
|
||||||
|
_newStatus int
|
||||||
|
oldResolution *int
|
||||||
|
_oldResolution int
|
||||||
|
newResolution *int
|
||||||
|
_newResolution int
|
||||||
|
eventType uint = model.EVENT_COMMENT
|
||||||
|
)
|
||||||
|
|
||||||
|
if input.Cmd != nil {
|
||||||
|
switch *input.Cmd {
|
||||||
|
case model.EmailCmdResolve:
|
||||||
|
if input.Resolution == nil {
|
||||||
|
return nil, errors.New("Resolution is required when cmd is RESOLVE")
|
||||||
|
}
|
||||||
|
eventType |= model.EVENT_STATUS_CHANGE
|
||||||
|
oldStatus = &_oldStatus
|
||||||
|
newStatus = &_newStatus
|
||||||
|
oldResolution = &_oldResolution
|
||||||
|
newResolution = &_newResolution
|
||||||
|
*oldStatus = ticket.Status().ToInt()
|
||||||
|
*oldResolution = ticket.Resolution().ToInt()
|
||||||
|
*newStatus = model.STATUS_RESOLVED
|
||||||
|
*newResolution = input.Resolution.ToInt()
|
||||||
|
updateTicket = updateTicket.
|
||||||
|
Set("status", *newStatus).
|
||||||
|
Set("resolution", *newResolution)
|
||||||
|
case model.EmailCmdReopen:
|
||||||
|
eventType |= model.EVENT_STATUS_CHANGE
|
||||||
|
oldStatus = &_oldStatus
|
||||||
|
newStatus = &_newStatus
|
||||||
|
oldResolution = &_oldResolution
|
||||||
|
newResolution = &_newResolution
|
||||||
|
*oldStatus = ticket.Status().ToInt()
|
||||||
|
*oldResolution = ticket.Resolution().ToInt()
|
||||||
|
*newStatus = model.STATUS_REPORTED
|
||||||
|
*newResolution = model.RESOLVED_UNRESOLVED
|
||||||
|
updateTicket = updateTicket.
|
||||||
|
Set("status", *newStatus).
|
||||||
|
Set("resolution", *newResolution)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var event model.Event
|
||||||
|
columns := database.Columns(ctx, &event)
|
||||||
|
if err := database.WithTx(ctx, nil, func(tx *sql.Tx) error {
|
||||||
|
_, err := updateTicket.
|
||||||
|
Set(`updated`, sq.Expr(`now() at time zone 'utc'`)).
|
||||||
|
Set(`comment_count`, sq.Expr(`comment_count + 1`)).
|
||||||
|
Where(`ticket.id = ?`, ticket.PKID).
|
||||||
|
RunWith(tx).
|
||||||
|
ExecContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
row := tx.QueryRowContext(ctx, `
|
||||||
|
INSERT INTO ticket_comment (
|
||||||
|
created, updated, submitter_id, ticket_id, text
|
||||||
|
) VALUES (
|
||||||
|
NOW() at time zone 'utc',
|
||||||
|
NOW() at time zone 'utc',
|
||||||
|
$1, $2, $3
|
||||||
|
) RETURNING id;
|
||||||
|
`, input.SenderID, ticket.PKID, input.Text)
|
||||||
|
|
||||||
|
var commentID int
|
||||||
|
if err := row.Scan(&commentID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.Cmd != nil {
|
||||||
|
switch *input.Cmd {
|
||||||
|
case model.EmailCmdLabel:
|
||||||
|
for _, labelID := range input.LabelIds {
|
||||||
|
var event model.Event
|
||||||
|
insertEvent := sq.Insert("event").
|
||||||
|
PlaceholderFormat(sq.Dollar).
|
||||||
|
Columns("created", "event_type", "ticket_id",
|
||||||
|
"participant_id", "label_id").
|
||||||
|
Values(sq.Expr("now() at time zone 'utc'"),
|
||||||
|
model.EVENT_LABEL_ADDED, ticket.PKID, input.SenderID, labelID)
|
||||||
|
|
||||||
|
_, err := tx.ExecContext(ctx, `
|
||||||
|
INSERT INTO ticket_label (
|
||||||
|
created, ticket_id, label_id, user_id
|
||||||
|
) VALUES (
|
||||||
|
NOW() at time zone 'utc',
|
||||||
|
$1, $2,
|
||||||
|
(SELECT user_id FROM participant WHERE id = $3)
|
||||||
|
)`, ticket.PKID, labelID, input.SenderID)
|
||||||
|
if err, ok := err.(*pq.Error); ok &&
|
||||||
|
err.Code == "23505" && // unique_violation
|
||||||
|
err.Constraint == "idx_label_ticket_unique" {
|
||||||
|
return errors.New("This label is already assigned to this ticket.")
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
row := insertEvent.
|
||||||
|
Suffix(`RETURNING ` + strings.Join(columns, ", ")).
|
||||||
|
RunWith(tx).
|
||||||
|
QueryRowContext(ctx)
|
||||||
|
if err := row.Scan(database.Scan(ctx, &event)...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := NewEventBuilder(ctx, tx, input.SenderID, model.EVENT_LABEL_ADDED).
|
||||||
|
WithTicket(tracker, ticket)
|
||||||
|
builder.InsertNotifications(event.ID, nil)
|
||||||
|
_, err = tx.ExecContext(ctx, `DROP TABLE event_participant;`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case model.EmailCmdUnlabel:
|
||||||
|
for _, labelID := range input.LabelIds {
|
||||||
|
var event model.Event
|
||||||
|
insertEvent := sq.Insert("event").
|
||||||
|
PlaceholderFormat(sq.Dollar).
|
||||||
|
Columns("created", "event_type", "ticket_id",
|
||||||
|
"participant_id", "label_id").
|
||||||
|
Values(sq.Expr("now() at time zone 'utc'"),
|
||||||
|
model.EVENT_LABEL_REMOVED, ticket.PKID, input.SenderID, labelID)
|
||||||
|
|
||||||
|
{
|
||||||
|
row := tx.QueryRowContext(ctx, `
|
||||||
|
DELETE FROM ticket_label
|
||||||
|
WHERE ticket_id = $1 AND label_id = $2
|
||||||
|
RETURNING 1`,
|
||||||
|
ticket.PKID, labelID)
|
||||||
|
var success bool
|
||||||
|
if err := row.Scan(&success); err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return errors.New("This label is not assigned to this ticket.")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
row := insertEvent.
|
||||||
|
Suffix(`RETURNING ` + strings.Join(columns, ", ")).
|
||||||
|
RunWith(tx).
|
||||||
|
QueryRowContext(ctx)
|
||||||
|
if err := row.Scan(database.Scan(ctx, &event)...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := NewEventBuilder(ctx, tx, input.SenderID, model.EVENT_LABEL_REMOVED).
|
||||||
|
WithTicket(tracker, ticket)
|
||||||
|
builder.InsertNotifications(event.ID, nil)
|
||||||
|
_, err = tx.ExecContext(ctx, `DROP TABLE event_participant;`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := NewEventBuilder(ctx, tx, input.SenderID, eventType).
|
||||||
|
WithTicket(tracker, ticket)
|
||||||
|
|
||||||
|
mentions := ScanMentions(ctx, tracker, ticket, input.Text)
|
||||||
|
builder.AddMentions(&mentions)
|
||||||
|
builder.InsertSubscriptions()
|
||||||
|
|
||||||
|
eventRow := insertEvent.Values(sq.Expr("now() at time zone 'utc'"),
|
||||||
|
eventType, ticket.PKID, input.SenderID, commentID,
|
||||||
|
oldStatus, newStatus, oldResolution, newResolution).
|
||||||
|
Suffix(`RETURNING ` + strings.Join(columns, ", ")).
|
||||||
|
RunWith(tx).
|
||||||
|
QueryRowContext(ctx)
|
||||||
|
if err := eventRow.Scan(database.Scan(ctx, &event)...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
builder.InsertNotifications(event.ID, &commentID)
|
||||||
|
|
||||||
|
conf := config.ForContext(ctx)
|
||||||
|
origin := config.GetOrigin(conf, "todo.sr.ht", true)
|
||||||
|
details := SubmitCommentDetails{
|
||||||
|
Root: origin,
|
||||||
|
TicketURL: fmt.Sprintf("/%s/%s/%d",
|
||||||
|
owner.CanonicalName(), tracker.Name, ticket.ID),
|
||||||
|
EventID: event.ID,
|
||||||
|
Comment: input.Text,
|
||||||
|
StatusUpdated: newStatus != nil,
|
||||||
|
}
|
||||||
|
if details.StatusUpdated {
|
||||||
|
details.Status = model.TicketStatusFromInt(*newStatus).String()
|
||||||
|
details.Resolution = model.TicketResolutionFromInt(*newResolution).String()
|
||||||
|
}
|
||||||
|
subject := fmt.Sprintf("Re: %s: %s", ticket.Ref(), ticket.Subject)
|
||||||
|
builder.SendEmails(subject, submitCommentTemplate, &details)
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
webhooks.DeliverLegacyEventCreate(ctx, tracker, ticket, &event)
|
||||||
|
webhooks.DeliverTrackerEventCreated(ctx, ticket.TrackerID, &event)
|
||||||
|
webhooks.DeliverTicketEventCreated(ctx, ticket.PKID, &event)
|
||||||
|
return &event, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) UpdateTicket(ctx context.Context, trackerID int, ticketID int, input map[string]interface{}) (*model.Ticket, error) {
|
func (r *mutationResolver) UpdateTicket(ctx context.Context, trackerID int, ticketID int, input map[string]interface{}) (*model.Ticket, error) {
|
||||||
if _, ok := input["import"]; ok {
|
if _, ok := input["import"]; ok {
|
||||||
panic(fmt.Errorf("not implemented")) // TODO
|
panic(fmt.Errorf("not implemented")) // TODO
|
||||||
|
|
|
@ -13,7 +13,6 @@ from todosrht.access import get_tracker, get_ticket
|
||||||
from todosrht.types import TicketAccess, TicketResolution, Tracker, Ticket, User
|
from todosrht.types import TicketAccess, TicketResolution, Tracker, Ticket, User
|
||||||
from todosrht.types import Label, TicketLabel, TicketSubscription, Event, EventType, ParticipantType
|
from todosrht.types import Label, TicketLabel, TicketSubscription, Event, EventType, ParticipantType
|
||||||
from todosrht.tickets import add_comment, get_participant_for_email
|
from todosrht.tickets import add_comment, get_participant_for_email
|
||||||
from todosrht.webhooks import UserWebhook, TrackerWebhook, TicketWebhook
|
|
||||||
from srht.validation import Validation
|
from srht.validation import Validation
|
||||||
import asyncio
|
import asyncio
|
||||||
import email
|
import email
|
||||||
|
@ -150,8 +149,8 @@ class MailHandler:
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = exec_gql("todo.sr.ht", """
|
resp = exec_gql("todo.sr.ht", """
|
||||||
mutation SubmitEmail($trackerId: Int!, $input: SubmitEmailInput!) {
|
mutation SubmitTicketEmail($trackerId: Int!, $input: SubmitTicketEmailInput!) {
|
||||||
submitEmail(trackerId: $trackerId, input: $input) {
|
submitTicketEmail(trackerId: $trackerId, input: $input) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,11 +160,8 @@ class MailHandler:
|
||||||
print("Rejecting email due to validation errors")
|
print("Rejecting email due to validation errors")
|
||||||
return "550 " + ", ".join([e.message for e in valid.errors])
|
return "550 " + ", ".join([e.message for e in valid.errors])
|
||||||
|
|
||||||
ticket, _ = get_ticket(tracker, resp["submitEmail"]["id"], user=sender.user)
|
ticket, _ = get_ticket(tracker, resp["submitTicketEmail"]["id"], user=sender.user)
|
||||||
|
|
||||||
TrackerWebhook.deliver(TrackerWebhook.Events.ticket_create,
|
|
||||||
ticket.to_dict(),
|
|
||||||
TrackerWebhook.Subscription.tracker_id == tracker.id)
|
|
||||||
print(f"Created ticket {ticket.ref()}")
|
print(f"Created ticket {ticket.ref()}")
|
||||||
return "250 Message accepted for delivery"
|
return "250 Message accepted for delivery"
|
||||||
|
|
||||||
|
@ -173,22 +169,34 @@ class MailHandler:
|
||||||
required_access = TicketAccess.comment
|
required_access = TicketAccess.comment
|
||||||
last_line = body.splitlines()[-1]
|
last_line = body.splitlines()[-1]
|
||||||
|
|
||||||
resolution = None
|
# Need to commit in case a new participant was created
|
||||||
resolve = reopen = False
|
db.session.commit()
|
||||||
|
|
||||||
|
valid = Validation({})
|
||||||
|
|
||||||
|
input = {
|
||||||
|
"text": body,
|
||||||
|
"senderId": sender.id,
|
||||||
|
}
|
||||||
|
|
||||||
cmds = ["!resolve", "!resolved", "!reopen"]
|
cmds = ["!resolve", "!resolved", "!reopen"]
|
||||||
if sender.participant_type == ParticipantType.user:
|
if sender.participant_type == ParticipantType.user:
|
||||||
# TODO: This should be possible via ACLs later
|
# TODO: This should be possible via ACLs later
|
||||||
cmds += ["!assign", "!label", "!unlabel"]
|
cmds += ["!assign", "!label", "!unlabel"]
|
||||||
if any(last_line.startswith(cmd) for cmd in cmds):
|
if any(last_line.startswith(cmd) for cmd in cmds):
|
||||||
cmd = shlex.split(last_line)
|
cmd = shlex.split(last_line)
|
||||||
body = body[:-len(last_line)-1].rstrip()
|
input["text"] = body.rstrip()[:-len(last_line)-1].rstrip()
|
||||||
required_access = TicketAccess.triage
|
required_access = TicketAccess.triage
|
||||||
if cmd[0] in ["!resolve", "!resolved"] and len(cmd) == 2:
|
if cmd[0] in ["!resolve", "!resolved"] and len(cmd) == 2:
|
||||||
resolve = True
|
input["cmd"] = "RESOLVE"
|
||||||
resolution = TicketResolution[cmd[1].lower()]
|
input["resolution"] = cmd[1].upper()
|
||||||
elif cmd[0] == "!reopen":
|
elif cmd[0] == "!reopen":
|
||||||
reopen = True
|
input["cmd"] = "REOPEN"
|
||||||
elif cmd[0] == "!label" or cmd[0] == "!unlabel":
|
elif cmd[0] == "!label" or cmd[0] == "!unlabel":
|
||||||
|
if cmd[0] == "!label":
|
||||||
|
input["cmd"] = "LABEL"
|
||||||
|
else:
|
||||||
|
input["cmd"] = "UNLABEL"
|
||||||
labels = Label.query.filter(
|
labels = Label.query.filter(
|
||||||
Label.name.in_(cmd[1:]),
|
Label.name.in_(cmd[1:]),
|
||||||
Label.tracker_id == ticket.tracker_id).all()
|
Label.tracker_id == ticket.tracker_id).all()
|
||||||
|
@ -199,33 +207,7 @@ class MailHandler:
|
||||||
print(f"Rejected, {sender.name} has insufficient " +
|
print(f"Rejected, {sender.name} has insufficient " +
|
||||||
f"permissions (have {access}, want triage)")
|
f"permissions (have {access}, want triage)")
|
||||||
return "550 You do not have permission to triage on this tracker."
|
return "550 You do not have permission to triage on this tracker."
|
||||||
for label in labels:
|
input["labelIds"] = [label.id for label in labels]
|
||||||
ticket_label = (TicketLabel.query
|
|
||||||
.filter(TicketLabel.label_id == label.id)
|
|
||||||
.filter(TicketLabel.ticket_id == ticket.id)).first()
|
|
||||||
event = Event()
|
|
||||||
event.participant_id = sender.id
|
|
||||||
event.ticket_id = ticket.id
|
|
||||||
event.label_id = label.id
|
|
||||||
if not ticket_label and cmd[0] == "!label":
|
|
||||||
# TODO: only supported for user participants
|
|
||||||
ticket_label = TicketLabel()
|
|
||||||
ticket_label.ticket_id = ticket.id
|
|
||||||
ticket_label.label_id = label.id
|
|
||||||
ticket_label.user_id = sender.id
|
|
||||||
db.session.add(ticket_label)
|
|
||||||
event.event_type = EventType.label_added
|
|
||||||
elif ticket_label and cmd[0] == "!unlabel":
|
|
||||||
db.session.delete(ticket_label)
|
|
||||||
event.event_type = EventType.label_removed
|
|
||||||
db.session.add(event)
|
|
||||||
db.session.commit()
|
|
||||||
TicketWebhook.deliver(TicketWebhook.Events.event_create,
|
|
||||||
event.to_dict(),
|
|
||||||
TicketWebhook.Subscription.ticket_id == ticket.id)
|
|
||||||
TrackerWebhook.deliver(TrackerWebhook.Events.event_create,
|
|
||||||
event.to_dict(),
|
|
||||||
TrackerWebhook.Subscription.tracker_id == ticket.tracker_id)
|
|
||||||
# TODO: Remaining commands
|
# TODO: Remaining commands
|
||||||
|
|
||||||
if not required_access in access:
|
if not required_access in access:
|
||||||
|
@ -237,14 +219,18 @@ class MailHandler:
|
||||||
print("Rejected, invalid comment length")
|
print("Rejected, invalid comment length")
|
||||||
return "550 Comment must be between 3 and 16384 characters."
|
return "550 Comment must be between 3 and 16384 characters."
|
||||||
|
|
||||||
event = add_comment(sender, ticket, text=body,
|
resp = exec_gql("todo.sr.ht", """
|
||||||
resolution=resolution, resolve=resolve, reopen=reopen, from_email=True)
|
mutation SubmitCommentEmail($trackerId: Int!, $ticketId: Int!, $input: SubmitCommentEmailInput!) {
|
||||||
TicketWebhook.deliver(TicketWebhook.Events.event_create,
|
submitCommentEmail(trackerId: $trackerId, ticketId: $ticketId, input: $input) {
|
||||||
event.to_dict(),
|
id
|
||||||
TicketWebhook.Subscription.ticket_id == ticket.id)
|
}
|
||||||
TrackerWebhook.deliver(TrackerWebhook.Events.event_create,
|
}
|
||||||
event.to_dict(),
|
""", user=ticket.tracker.owner, valid=valid, trackerId=ticket.tracker_id, ticketId=ticket.scoped_id, input=input)
|
||||||
TrackerWebhook.Subscription.tracker_id == ticket.tracker_id)
|
|
||||||
|
if not valid.ok:
|
||||||
|
print("Rejecting email due to validation errors")
|
||||||
|
return "550 " + ", ".join([e.message for e in valid.errors])
|
||||||
|
|
||||||
print(f"Added comment to {ticket.ref()}")
|
print(f"Added comment to {ticket.ref()}")
|
||||||
return "250 Message accepted for delivery"
|
return "250 Message accepted for delivery"
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue