Add forms question management and watch commands
New commands: add-question, delete-question, move-question, update, and watch create/list/delete/renew. Covers the full Google Forms API surface for form modification via batchUpdate and response watches. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4a69ebbfa5
commit
1de23055b8
@ -16,9 +16,14 @@ import (
|
||||
var newFormsService = googleapi.NewForms
|
||||
|
||||
type FormsCmd struct {
|
||||
Get FormsGetCmd `cmd:"" name:"get" aliases:"info,show" help:"Get a form"`
|
||||
Create FormsCreateCmd `cmd:"" name:"create" aliases:"new" help:"Create a form"`
|
||||
Responses FormsResponsesCmd `cmd:"" name:"responses" help:"Form responses"`
|
||||
Get FormsGetCmd `cmd:"" name:"get" aliases:"info,show" help:"Get a form"`
|
||||
Create FormsCreateCmd `cmd:"" name:"create" aliases:"new" help:"Create a form"`
|
||||
Update FormsUpdateCmd `cmd:"" name:"update" aliases:"edit" help:"Update form title, description, or settings"`
|
||||
AddQuestion FormsAddQuestionCmd `cmd:"" name:"add-question" aliases:"add-q,aq" help:"Add a question to a form"`
|
||||
DeleteQuestion FormsDeleteQuestionCmd `cmd:"" name:"delete-question" aliases:"delete-q,dq,rm-q" help:"Delete a question by index"`
|
||||
MoveQuestion FormsMoveQuestionCmd `cmd:"" name:"move-question" aliases:"move-q,mq" help:"Move a question to a new position"`
|
||||
Responses FormsResponsesCmd `cmd:"" name:"responses" help:"Form responses"`
|
||||
Watch FormsWatchCmd `cmd:"" name:"watch" aliases:"watches" help:"Response watches (push notifications)"`
|
||||
}
|
||||
|
||||
type FormsResponsesCmd struct {
|
||||
@ -111,6 +116,13 @@ func (c *FormsCreateCmd) Run(ctx context.Context, flags *RootFlags) error {
|
||||
u := ui.FromContext(ctx)
|
||||
u.Out().Printf("created\ttrue")
|
||||
printFormSummary(u, form, formID)
|
||||
u.Err().Println("")
|
||||
u.Err().Println("# Tip: Email notifications for new responses must be enabled manually:")
|
||||
u.Err().Println("# 1. Open the edit URL above in your browser")
|
||||
u.Err().Println("# 2. Click the Responses tab")
|
||||
u.Err().Println("# 3. Click the three-dot menu (⋮)")
|
||||
u.Err().Println("# 4. Toggle 'Get email notifications for new responses'")
|
||||
u.Err().Println("# This setting is not available via the API.")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
426
internal/cmd/forms_modify.go
Normal file
426
internal/cmd/forms_modify.go
Normal file
@ -0,0 +1,426 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
formsapi "google.golang.org/api/forms/v1"
|
||||
|
||||
"github.com/steipete/gogcli/internal/outfmt"
|
||||
"github.com/steipete/gogcli/internal/ui"
|
||||
)
|
||||
|
||||
// FormsAddQuestionCmd adds a question to an existing form via batchUpdate.
|
||||
type FormsAddQuestionCmd struct {
|
||||
FormID string `arg:"" name:"formId" help:"Form ID"`
|
||||
Title string `name:"title" help:"Question title/text" required:""`
|
||||
Type string `name:"type" help:"Question type: text|paragraph|radio|checkbox|dropdown|scale|date|time" default:"text"`
|
||||
Required bool `name:"required" help:"Whether an answer is required"`
|
||||
Options []string `name:"option" help:"Choice options (for radio/checkbox/dropdown, repeat for each)" short:"o"`
|
||||
Index int `name:"index" help:"Position to insert (0-based, default append)" default:"-1"`
|
||||
|
||||
// Scale-specific
|
||||
ScaleLow int `name:"scale-low" help:"Scale minimum value" default:"1"`
|
||||
ScaleHigh int `name:"scale-high" help:"Scale maximum value" default:"5"`
|
||||
ScaleLowLabel string `name:"scale-low-label" help:"Label for low end of scale"`
|
||||
ScaleHighLabel string `name:"scale-high-label" help:"Label for high end of scale"`
|
||||
|
||||
// Date/time specific
|
||||
IncludeTime bool `name:"include-time" help:"Include time picker (for date type)"`
|
||||
IncludeYear bool `name:"include-year" help:"Include year field (for date type)"`
|
||||
Duration bool `name:"duration" help:"Ask for duration instead of time (for time type)"`
|
||||
|
||||
Description string `name:"description" help:"Question description/help text"`
|
||||
}
|
||||
|
||||
func (c *FormsAddQuestionCmd) Run(ctx context.Context, flags *RootFlags) error {
|
||||
account, err := requireAccount(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
formID := strings.TrimSpace(normalizeGoogleID(c.FormID))
|
||||
if formID == "" {
|
||||
return usage("empty formId")
|
||||
}
|
||||
title := strings.TrimSpace(c.Title)
|
||||
if title == "" {
|
||||
return usage("empty --title")
|
||||
}
|
||||
qType := strings.ToLower(strings.TrimSpace(c.Type))
|
||||
|
||||
question, err := buildQuestion(qType, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dryRunErr := dryRunExit(ctx, flags, "forms.addQuestion", map[string]any{
|
||||
"form_id": formID,
|
||||
"title": title,
|
||||
"type": qType,
|
||||
"required": c.Required,
|
||||
"options": c.Options,
|
||||
"index": c.Index,
|
||||
"description": c.Description,
|
||||
}); dryRunErr != nil {
|
||||
return dryRunErr
|
||||
}
|
||||
|
||||
svc, err := newFormsService(ctx, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
item := &formsapi.Item{
|
||||
Title: title,
|
||||
Description: strings.TrimSpace(c.Description),
|
||||
QuestionItem: &formsapi.QuestionItem{
|
||||
Question: question,
|
||||
},
|
||||
}
|
||||
|
||||
createReq := &formsapi.CreateItemRequest{
|
||||
Item: item,
|
||||
}
|
||||
|
||||
// Determine the insertion index. The API requires a Location.
|
||||
// For index 0 we must use ForceSendFields since 0 is Go's zero-value.
|
||||
var insertAt int64
|
||||
if c.Index >= 0 {
|
||||
insertAt = int64(c.Index)
|
||||
} else {
|
||||
// Append: get the form to find current item count.
|
||||
currentForm, getErr := svc.Forms.Get(formID).Context(ctx).Do()
|
||||
if getErr != nil {
|
||||
return getErr
|
||||
}
|
||||
insertAt = int64(len(currentForm.Items))
|
||||
}
|
||||
createReq.Location = &formsapi.Location{
|
||||
Index: insertAt,
|
||||
ForceSendFields: []string{"Index"},
|
||||
}
|
||||
|
||||
batchReq := &formsapi.BatchUpdateFormRequest{
|
||||
Requests: []*formsapi.Request{
|
||||
{CreateItem: createReq},
|
||||
},
|
||||
IncludeFormInResponse: true,
|
||||
}
|
||||
|
||||
resp, err := svc.Forms.BatchUpdate(formID, batchReq).Context(ctx).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Determine the actual index used for output.
|
||||
var insertIndex int64 = -1
|
||||
if createReq.Location != nil {
|
||||
insertIndex = createReq.Location.Index
|
||||
} else if resp.Form != nil {
|
||||
// Appended — index is last item position.
|
||||
insertIndex = int64(len(resp.Form.Items) - 1)
|
||||
}
|
||||
|
||||
if outfmt.IsJSON(ctx) {
|
||||
return outfmt.WriteJSON(ctx, os.Stdout, map[string]any{
|
||||
"created": true,
|
||||
"form_id": formID,
|
||||
"title": title,
|
||||
"type": qType,
|
||||
"index": insertIndex,
|
||||
"form": resp.Form,
|
||||
"edit_url": formEditURL(formID),
|
||||
})
|
||||
}
|
||||
|
||||
u := ui.FromContext(ctx)
|
||||
u.Out().Printf("created\ttrue")
|
||||
u.Out().Printf("form_id\t%s", formID)
|
||||
u.Out().Printf("question\t%s", title)
|
||||
u.Out().Printf("type\t%s", qType)
|
||||
u.Out().Printf("index\t%d", insertIndex)
|
||||
u.Out().Printf("edit_url\t%s", formEditURL(formID))
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildQuestion(qType string, c *FormsAddQuestionCmd) (*formsapi.Question, error) {
|
||||
q := &formsapi.Question{
|
||||
Required: c.Required,
|
||||
}
|
||||
|
||||
switch qType {
|
||||
case "text":
|
||||
q.TextQuestion = &formsapi.TextQuestion{Paragraph: false}
|
||||
case "paragraph":
|
||||
q.TextQuestion = &formsapi.TextQuestion{Paragraph: true}
|
||||
case "radio", "checkbox", "dropdown":
|
||||
if len(c.Options) == 0 {
|
||||
return nil, usage("--option is required for " + qType + " questions (repeat for each choice)")
|
||||
}
|
||||
apiType := map[string]string{
|
||||
"radio": "RADIO",
|
||||
"checkbox": "CHECKBOX",
|
||||
"dropdown": "DROP_DOWN",
|
||||
}[qType]
|
||||
opts := make([]*formsapi.Option, len(c.Options))
|
||||
for i, v := range c.Options {
|
||||
opts[i] = &formsapi.Option{Value: v}
|
||||
}
|
||||
q.ChoiceQuestion = &formsapi.ChoiceQuestion{
|
||||
Type: apiType,
|
||||
Options: opts,
|
||||
}
|
||||
case "scale":
|
||||
q.ScaleQuestion = &formsapi.ScaleQuestion{
|
||||
Low: int64(c.ScaleLow),
|
||||
High: int64(c.ScaleHigh),
|
||||
LowLabel: c.ScaleLowLabel,
|
||||
HighLabel: c.ScaleHighLabel,
|
||||
}
|
||||
case "date":
|
||||
q.DateQuestion = &formsapi.DateQuestion{
|
||||
IncludeTime: c.IncludeTime,
|
||||
IncludeYear: c.IncludeYear,
|
||||
}
|
||||
case "time":
|
||||
q.TimeQuestion = &formsapi.TimeQuestion{
|
||||
Duration: c.Duration,
|
||||
}
|
||||
default:
|
||||
return nil, usage("unknown question type: " + qType + " (use text|paragraph|radio|checkbox|dropdown|scale|date|time)")
|
||||
}
|
||||
|
||||
return q, nil
|
||||
}
|
||||
|
||||
// FormsDeleteQuestionCmd removes a question from a form by index.
|
||||
type FormsDeleteQuestionCmd struct {
|
||||
FormID string `arg:"" name:"formId" help:"Form ID"`
|
||||
Index int `arg:"" name:"index" help:"Question index (0-based)"`
|
||||
}
|
||||
|
||||
func (c *FormsDeleteQuestionCmd) Run(ctx context.Context, flags *RootFlags) error {
|
||||
account, err := requireAccount(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
formID := strings.TrimSpace(normalizeGoogleID(c.FormID))
|
||||
if formID == "" {
|
||||
return usage("empty formId")
|
||||
}
|
||||
if c.Index < 0 {
|
||||
return usage("index must be >= 0")
|
||||
}
|
||||
|
||||
if dryRunErr := dryRunExit(ctx, flags, "forms.deleteQuestion", map[string]any{
|
||||
"form_id": formID,
|
||||
"index": c.Index,
|
||||
}); dryRunErr != nil {
|
||||
return dryRunErr
|
||||
}
|
||||
|
||||
svc, err := newFormsService(ctx, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
batchReq := &formsapi.BatchUpdateFormRequest{
|
||||
Requests: []*formsapi.Request{
|
||||
{
|
||||
DeleteItem: &formsapi.DeleteItemRequest{
|
||||
Location: &formsapi.Location{Index: int64(c.Index)},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err = svc.Forms.BatchUpdate(formID, batchReq).Context(ctx).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if outfmt.IsJSON(ctx) {
|
||||
return outfmt.WriteJSON(ctx, os.Stdout, map[string]any{
|
||||
"deleted": true,
|
||||
"form_id": formID,
|
||||
"index": c.Index,
|
||||
})
|
||||
}
|
||||
|
||||
u := ui.FromContext(ctx)
|
||||
u.Out().Printf("deleted\ttrue")
|
||||
u.Out().Printf("form_id\t%s", formID)
|
||||
u.Out().Printf("index\t%d", c.Index)
|
||||
return nil
|
||||
}
|
||||
|
||||
// FormsMoveQuestionCmd moves a question to a new position.
|
||||
type FormsMoveQuestionCmd struct {
|
||||
FormID string `arg:"" name:"formId" help:"Form ID"`
|
||||
OldIndex int `arg:"" name:"oldIndex" help:"Current question index (0-based)"`
|
||||
NewIndex int `arg:"" name:"newIndex" help:"Target question index (0-based)"`
|
||||
}
|
||||
|
||||
func (c *FormsMoveQuestionCmd) Run(ctx context.Context, flags *RootFlags) error {
|
||||
account, err := requireAccount(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
formID := strings.TrimSpace(normalizeGoogleID(c.FormID))
|
||||
if formID == "" {
|
||||
return usage("empty formId")
|
||||
}
|
||||
if c.OldIndex < 0 || c.NewIndex < 0 {
|
||||
return usage("indices must be >= 0")
|
||||
}
|
||||
|
||||
if dryRunErr := dryRunExit(ctx, flags, "forms.moveQuestion", map[string]any{
|
||||
"form_id": formID,
|
||||
"old_index": c.OldIndex,
|
||||
"new_index": c.NewIndex,
|
||||
}); dryRunErr != nil {
|
||||
return dryRunErr
|
||||
}
|
||||
|
||||
svc, err := newFormsService(ctx, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
batchReq := &formsapi.BatchUpdateFormRequest{
|
||||
Requests: []*formsapi.Request{
|
||||
{
|
||||
MoveItem: &formsapi.MoveItemRequest{
|
||||
OriginalLocation: &formsapi.Location{Index: int64(c.OldIndex)},
|
||||
NewLocation: &formsapi.Location{Index: int64(c.NewIndex)},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err = svc.Forms.BatchUpdate(formID, batchReq).Context(ctx).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if outfmt.IsJSON(ctx) {
|
||||
return outfmt.WriteJSON(ctx, os.Stdout, map[string]any{
|
||||
"moved": true,
|
||||
"form_id": formID,
|
||||
"old_index": c.OldIndex,
|
||||
"new_index": c.NewIndex,
|
||||
})
|
||||
}
|
||||
|
||||
u := ui.FromContext(ctx)
|
||||
u.Out().Printf("moved\ttrue")
|
||||
u.Out().Printf("form_id\t%s", formID)
|
||||
u.Out().Printf("old_index\t%d", c.OldIndex)
|
||||
u.Out().Printf("new_index\t%d", c.NewIndex)
|
||||
return nil
|
||||
}
|
||||
|
||||
// FormsUpdateCmd modifies form title, description, or settings.
|
||||
type FormsUpdateCmd struct {
|
||||
FormID string `arg:"" name:"formId" help:"Form ID"`
|
||||
Title string `name:"title" help:"New form title"`
|
||||
Description string `name:"description" help:"New form description"`
|
||||
IsQuiz string `name:"quiz" help:"Enable quiz mode (true/false)"`
|
||||
}
|
||||
|
||||
func (c *FormsUpdateCmd) Run(ctx context.Context, flags *RootFlags) error {
|
||||
account, err := requireAccount(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
formID := strings.TrimSpace(normalizeGoogleID(c.FormID))
|
||||
if formID == "" {
|
||||
return usage("empty formId")
|
||||
}
|
||||
|
||||
title := strings.TrimSpace(c.Title)
|
||||
description := strings.TrimSpace(c.Description)
|
||||
quiz := strings.TrimSpace(strings.ToLower(c.IsQuiz))
|
||||
|
||||
if title == "" && description == "" && quiz == "" {
|
||||
return usage("at least one of --title, --description, or --quiz is required")
|
||||
}
|
||||
|
||||
if dryRunErr := dryRunExit(ctx, flags, "forms.update", map[string]any{
|
||||
"form_id": formID,
|
||||
"title": title,
|
||||
"description": description,
|
||||
"quiz": quiz,
|
||||
}); dryRunErr != nil {
|
||||
return dryRunErr
|
||||
}
|
||||
|
||||
svc, err := newFormsService(ctx, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var requests []*formsapi.Request
|
||||
|
||||
if title != "" || description != "" {
|
||||
info := &formsapi.Info{}
|
||||
var masks []string
|
||||
if title != "" {
|
||||
info.Title = title
|
||||
masks = append(masks, "title")
|
||||
}
|
||||
if description != "" {
|
||||
info.Description = description
|
||||
masks = append(masks, "description")
|
||||
}
|
||||
requests = append(requests, &formsapi.Request{
|
||||
UpdateFormInfo: &formsapi.UpdateFormInfoRequest{
|
||||
Info: info,
|
||||
UpdateMask: strings.Join(masks, ","),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if quiz != "" {
|
||||
isQuiz, parseErr := strconv.ParseBool(quiz)
|
||||
if parseErr != nil {
|
||||
return usage("--quiz must be true or false")
|
||||
}
|
||||
requests = append(requests, &formsapi.Request{
|
||||
UpdateSettings: &formsapi.UpdateSettingsRequest{
|
||||
Settings: &formsapi.FormSettings{
|
||||
QuizSettings: &formsapi.QuizSettings{
|
||||
IsQuiz: isQuiz,
|
||||
},
|
||||
},
|
||||
UpdateMask: "quizSettings.isQuiz",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
batchReq := &formsapi.BatchUpdateFormRequest{
|
||||
Requests: requests,
|
||||
IncludeFormInResponse: true,
|
||||
}
|
||||
|
||||
resp, err := svc.Forms.BatchUpdate(formID, batchReq).Context(ctx).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if outfmt.IsJSON(ctx) {
|
||||
return outfmt.WriteJSON(ctx, os.Stdout, map[string]any{
|
||||
"updated": true,
|
||||
"form_id": formID,
|
||||
"form": resp.Form,
|
||||
"edit_url": formEditURL(formID),
|
||||
})
|
||||
}
|
||||
|
||||
u := ui.FromContext(ctx)
|
||||
u.Out().Printf("updated\ttrue")
|
||||
printFormSummary(u, resp.Form, formID)
|
||||
return nil
|
||||
}
|
||||
241
internal/cmd/forms_watch.go
Normal file
241
internal/cmd/forms_watch.go
Normal file
@ -0,0 +1,241 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
formsapi "google.golang.org/api/forms/v1"
|
||||
|
||||
"github.com/steipete/gogcli/internal/outfmt"
|
||||
"github.com/steipete/gogcli/internal/ui"
|
||||
)
|
||||
|
||||
// FormsWatchCmd groups watch subcommands.
|
||||
type FormsWatchCmd struct {
|
||||
Create FormsWatchCreateCmd `cmd:"" name:"create" aliases:"new,add" help:"Create a watch for new responses"`
|
||||
List FormsWatchListCmd `cmd:"" name:"list" aliases:"ls" help:"List active watches"`
|
||||
Delete FormsWatchDeleteCmd `cmd:"" name:"delete" aliases:"rm,remove" help:"Delete a watch"`
|
||||
Renew FormsWatchRenewCmd `cmd:"" name:"renew" aliases:"refresh" help:"Renew a watch (extends 7 days)"`
|
||||
}
|
||||
|
||||
// FormsWatchCreateCmd creates a push notification watch on form responses.
|
||||
type FormsWatchCreateCmd struct {
|
||||
FormID string `arg:"" name:"formId" help:"Form ID"`
|
||||
TopicID string `name:"topic" help:"Cloud Pub/Sub topic name (projects/{project}/topics/{topic})" required:""`
|
||||
EventType string `name:"event-type" help:"Event type to watch" default:"RESPONSES" enum:"RESPONSES,SCHEMA"`
|
||||
}
|
||||
|
||||
func (c *FormsWatchCreateCmd) Run(ctx context.Context, flags *RootFlags) error {
|
||||
account, err := requireAccount(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
formID := strings.TrimSpace(normalizeGoogleID(c.FormID))
|
||||
if formID == "" {
|
||||
return usage("empty formId")
|
||||
}
|
||||
topicID := strings.TrimSpace(c.TopicID)
|
||||
if topicID == "" {
|
||||
return usage("empty --topic")
|
||||
}
|
||||
|
||||
if dryRunErr := dryRunExit(ctx, flags, "forms.watches.create", map[string]any{
|
||||
"form_id": formID,
|
||||
"topic": topicID,
|
||||
"event_type": c.EventType,
|
||||
}); dryRunErr != nil {
|
||||
return dryRunErr
|
||||
}
|
||||
|
||||
svc, err := newFormsService(ctx, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := &formsapi.CreateWatchRequest{
|
||||
Watch: &formsapi.Watch{
|
||||
Target: &formsapi.WatchTarget{
|
||||
Topic: &formsapi.CloudPubsubTopic{
|
||||
TopicName: topicID,
|
||||
},
|
||||
},
|
||||
EventType: c.EventType,
|
||||
},
|
||||
}
|
||||
|
||||
watch, err := svc.Forms.Watches.Create(formID, req).Context(ctx).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if outfmt.IsJSON(ctx) {
|
||||
return outfmt.WriteJSON(ctx, os.Stdout, map[string]any{
|
||||
"created": true,
|
||||
"form_id": formID,
|
||||
"watch": watch,
|
||||
})
|
||||
}
|
||||
|
||||
u := ui.FromContext(ctx)
|
||||
u.Out().Printf("created\ttrue")
|
||||
u.Out().Printf("watch_id\t%s", watch.Id)
|
||||
u.Out().Printf("form_id\t%s", formID)
|
||||
u.Out().Printf("event_type\t%s", watch.EventType)
|
||||
u.Out().Printf("state\t%s", watch.State)
|
||||
if watch.ExpireTime != "" {
|
||||
u.Out().Printf("expires\t%s", watch.ExpireTime)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FormsWatchListCmd lists active watches for a form.
|
||||
type FormsWatchListCmd struct {
|
||||
FormID string `arg:"" name:"formId" help:"Form ID"`
|
||||
}
|
||||
|
||||
func (c *FormsWatchListCmd) Run(ctx context.Context, flags *RootFlags) error {
|
||||
account, err := requireAccount(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
formID := strings.TrimSpace(normalizeGoogleID(c.FormID))
|
||||
if formID == "" {
|
||||
return usage("empty formId")
|
||||
}
|
||||
|
||||
svc, err := newFormsService(ctx, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := svc.Forms.Watches.List(formID).Context(ctx).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if outfmt.IsJSON(ctx) {
|
||||
return outfmt.WriteJSON(ctx, os.Stdout, map[string]any{
|
||||
"form_id": formID,
|
||||
"watches": resp.Watches,
|
||||
})
|
||||
}
|
||||
|
||||
u := ui.FromContext(ctx)
|
||||
if len(resp.Watches) == 0 {
|
||||
u.Out().Println("No active watches.")
|
||||
return nil
|
||||
}
|
||||
u.Out().Println("WATCH_ID\tEVENT_TYPE\tSTATE\tEXPIRES")
|
||||
for _, w := range resp.Watches {
|
||||
if w == nil {
|
||||
continue
|
||||
}
|
||||
u.Out().Printf("%s\t%s\t%s\t%s", w.Id, w.EventType, w.State, w.ExpireTime)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FormsWatchDeleteCmd removes a watch.
|
||||
type FormsWatchDeleteCmd struct {
|
||||
FormID string `arg:"" name:"formId" help:"Form ID"`
|
||||
WatchID string `arg:"" name:"watchId" help:"Watch ID"`
|
||||
}
|
||||
|
||||
func (c *FormsWatchDeleteCmd) Run(ctx context.Context, flags *RootFlags) error {
|
||||
account, err := requireAccount(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
formID := strings.TrimSpace(normalizeGoogleID(c.FormID))
|
||||
if formID == "" {
|
||||
return usage("empty formId")
|
||||
}
|
||||
watchID := strings.TrimSpace(c.WatchID)
|
||||
if watchID == "" {
|
||||
return usage("empty watchId")
|
||||
}
|
||||
|
||||
if dryRunErr := dryRunExit(ctx, flags, "forms.watches.delete", map[string]any{
|
||||
"form_id": formID,
|
||||
"watch_id": watchID,
|
||||
}); dryRunErr != nil {
|
||||
return dryRunErr
|
||||
}
|
||||
|
||||
svc, err := newFormsService(ctx, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := svc.Forms.Watches.Delete(formID, watchID).Context(ctx).Do(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if outfmt.IsJSON(ctx) {
|
||||
return outfmt.WriteJSON(ctx, os.Stdout, map[string]any{
|
||||
"deleted": true,
|
||||
"form_id": formID,
|
||||
"watch_id": watchID,
|
||||
})
|
||||
}
|
||||
|
||||
u := ui.FromContext(ctx)
|
||||
u.Out().Printf("deleted\ttrue")
|
||||
u.Out().Printf("form_id\t%s", formID)
|
||||
u.Out().Printf("watch_id\t%s", watchID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// FormsWatchRenewCmd renews an existing watch for another 7 days.
|
||||
type FormsWatchRenewCmd struct {
|
||||
FormID string `arg:"" name:"formId" help:"Form ID"`
|
||||
WatchID string `arg:"" name:"watchId" help:"Watch ID"`
|
||||
}
|
||||
|
||||
func (c *FormsWatchRenewCmd) Run(ctx context.Context, flags *RootFlags) error {
|
||||
account, err := requireAccount(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
formID := strings.TrimSpace(normalizeGoogleID(c.FormID))
|
||||
if formID == "" {
|
||||
return usage("empty formId")
|
||||
}
|
||||
watchID := strings.TrimSpace(c.WatchID)
|
||||
if watchID == "" {
|
||||
return usage("empty watchId")
|
||||
}
|
||||
|
||||
if dryRunErr := dryRunExit(ctx, flags, "forms.watches.renew", map[string]any{
|
||||
"form_id": formID,
|
||||
"watch_id": watchID,
|
||||
}); dryRunErr != nil {
|
||||
return dryRunErr
|
||||
}
|
||||
|
||||
svc, err := newFormsService(ctx, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
watch, err := svc.Forms.Watches.Renew(formID, watchID, &formsapi.RenewWatchRequest{}).Context(ctx).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if outfmt.IsJSON(ctx) {
|
||||
return outfmt.WriteJSON(ctx, os.Stdout, map[string]any{
|
||||
"renewed": true,
|
||||
"form_id": formID,
|
||||
"watch": watch,
|
||||
})
|
||||
}
|
||||
|
||||
u := ui.FromContext(ctx)
|
||||
u.Out().Printf("renewed\ttrue")
|
||||
u.Out().Printf("watch_id\t%s", watch.Id)
|
||||
u.Out().Printf("form_id\t%s", formID)
|
||||
u.Out().Printf("expires\t%s", watch.ExpireTime)
|
||||
return nil
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user