github.com/wfusion/gofusion@v1.1.14/common/infra/asynq/asynqmon/task_handlers.go (about)

     1  package asynqmon
     2  
     3  import (
     4  	"errors"
     5  	"log"
     6  	"net/http"
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/gorilla/mux"
    12  
    13  	"github.com/wfusion/gofusion/common/infra/asynq"
    14  	"github.com/wfusion/gofusion/common/utils/serialize/json"
    15  )
    16  
    17  // ****************************************************************************
    18  // This file defines:
    19  //   - http.Handler(s) for task related endpoints
    20  // ****************************************************************************
    21  
    22  type listActiveTasksResponse struct {
    23  	Tasks []*activeTask       `json:"tasks"`
    24  	Stats *queueStateSnapshot `json:"stats"`
    25  }
    26  
    27  func newListActiveTasksHandlerFunc(inspector *asynq.Inspector, pf PayloadFormatter) http.HandlerFunc {
    28  	return func(w http.ResponseWriter, r *http.Request) {
    29  		vars := mux.Vars(r)
    30  		qname := vars["qname"]
    31  		pageSize, pageNum := getPageOptions(r)
    32  
    33  		tasks, err := inspector.ListActiveTasks(
    34  			qname, asynq.PageSize(pageSize), asynq.Page(pageNum))
    35  		if err != nil {
    36  			http.Error(w, err.Error(), http.StatusInternalServerError)
    37  			return
    38  		}
    39  		qinfo, err := inspector.GetQueueInfo(qname)
    40  		if err != nil {
    41  			http.Error(w, err.Error(), http.StatusInternalServerError)
    42  			return
    43  		}
    44  		servers, err := inspector.Servers()
    45  		if err != nil {
    46  			http.Error(w, err.Error(), http.StatusInternalServerError)
    47  			return
    48  		}
    49  		// m maps taskID to workerInfo.
    50  		m := make(map[string]*asynq.WorkerInfo)
    51  		for _, srv := range servers {
    52  			for _, w := range srv.ActiveWorkers {
    53  				if w.Queue == qname {
    54  					m[w.TaskID] = w
    55  				}
    56  			}
    57  		}
    58  		activeTasks := toActiveTasks(tasks, pf)
    59  		for _, t := range activeTasks {
    60  			workerInfo, ok := m[t.ID]
    61  			if ok {
    62  				t.Started = workerInfo.Started.Format(time.RFC3339)
    63  				t.Deadline = workerInfo.Deadline.Format(time.RFC3339)
    64  			} else {
    65  				t.Started = "-"
    66  				t.Deadline = "-"
    67  			}
    68  		}
    69  
    70  		resp := listActiveTasksResponse{
    71  			Tasks: activeTasks,
    72  			Stats: toQueueStateSnapshot(qinfo),
    73  		}
    74  		writeResponseJSON(w, resp)
    75  	}
    76  }
    77  
    78  func newCancelActiveTaskHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
    79  	return func(w http.ResponseWriter, r *http.Request) {
    80  		id := mux.Vars(r)["task_id"]
    81  		if err := inspector.CancelProcessing(id); err != nil {
    82  			http.Error(w, err.Error(), http.StatusInternalServerError)
    83  			return
    84  		}
    85  		w.WriteHeader(http.StatusNoContent)
    86  	}
    87  }
    88  
    89  func newCancelAllActiveTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
    90  	return func(w http.ResponseWriter, r *http.Request) {
    91  		const batchSize = 100
    92  		page := 1
    93  		qname := mux.Vars(r)["qname"]
    94  		for {
    95  			tasks, err := inspector.ListActiveTasks(qname, asynq.Page(page), asynq.PageSize(batchSize))
    96  			if err != nil {
    97  				http.Error(w, err.Error(), http.StatusInternalServerError)
    98  				return
    99  			}
   100  			for _, t := range tasks {
   101  				if err := inspector.CancelProcessing(t.ID); err != nil {
   102  					http.Error(w, err.Error(), http.StatusInternalServerError)
   103  					return
   104  				}
   105  			}
   106  			if len(tasks) < batchSize {
   107  				break
   108  			}
   109  			page++
   110  		}
   111  		w.WriteHeader(http.StatusNoContent)
   112  	}
   113  }
   114  
   115  type batchCancelTasksRequest struct {
   116  	TaskIDs []string `json:"task_ids"`
   117  }
   118  
   119  type batchCancelTasksResponse struct {
   120  	CanceledIDs []string `json:"canceled_ids"`
   121  	ErrorIDs    []string `json:"error_ids"`
   122  }
   123  
   124  func newBatchCancelActiveTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   125  	return func(w http.ResponseWriter, r *http.Request) {
   126  		r.Body = http.MaxBytesReader(w, r.Body, maxRequestBodySize)
   127  		dec := json.NewDecoder(r.Body)
   128  		dec.DisallowUnknownFields()
   129  
   130  		var req batchCancelTasksRequest
   131  		if err := dec.Decode(&req); err != nil {
   132  			http.Error(w, err.Error(), http.StatusBadRequest)
   133  			return
   134  		}
   135  
   136  		resp := batchCancelTasksResponse{
   137  			// avoid null in the json response
   138  			CanceledIDs: make([]string, 0),
   139  			ErrorIDs:    make([]string, 0),
   140  		}
   141  		for _, id := range req.TaskIDs {
   142  			if err := inspector.CancelProcessing(id); err != nil {
   143  				log.Printf("error: could not send cancelation signal to task %s", id)
   144  				resp.ErrorIDs = append(resp.ErrorIDs, id)
   145  			} else {
   146  				resp.CanceledIDs = append(resp.CanceledIDs, id)
   147  			}
   148  		}
   149  		writeResponseJSON(w, resp)
   150  	}
   151  }
   152  
   153  func newListPendingTasksHandlerFunc(inspector *asynq.Inspector, pf PayloadFormatter) http.HandlerFunc {
   154  	return func(w http.ResponseWriter, r *http.Request) {
   155  		vars := mux.Vars(r)
   156  		qname := vars["qname"]
   157  		pageSize, pageNum := getPageOptions(r)
   158  		tasks, err := inspector.ListPendingTasks(
   159  			qname, asynq.PageSize(pageSize), asynq.Page(pageNum))
   160  		if err != nil {
   161  			http.Error(w, err.Error(), http.StatusInternalServerError)
   162  			return
   163  		}
   164  		qinfo, err := inspector.GetQueueInfo(qname)
   165  		if err != nil {
   166  			http.Error(w, err.Error(), http.StatusInternalServerError)
   167  			return
   168  		}
   169  		payload := make(map[string]any)
   170  		if len(tasks) == 0 {
   171  			// avoid nil for the tasks field in json output.
   172  			payload["tasks"] = make([]*pendingTask, 0)
   173  		} else {
   174  			payload["tasks"] = toPendingTasks(tasks, pf)
   175  		}
   176  		payload["stats"] = toQueueStateSnapshot(qinfo)
   177  		writeResponseJSON(w, payload)
   178  	}
   179  }
   180  
   181  func newListScheduledTasksHandlerFunc(inspector *asynq.Inspector, pf PayloadFormatter) http.HandlerFunc {
   182  	return func(w http.ResponseWriter, r *http.Request) {
   183  		vars := mux.Vars(r)
   184  		qname := vars["qname"]
   185  		pageSize, pageNum := getPageOptions(r)
   186  		tasks, err := inspector.ListScheduledTasks(
   187  			qname, asynq.PageSize(pageSize), asynq.Page(pageNum))
   188  		if err != nil {
   189  			http.Error(w, err.Error(), http.StatusInternalServerError)
   190  			return
   191  		}
   192  		qinfo, err := inspector.GetQueueInfo(qname)
   193  		if err != nil {
   194  			http.Error(w, err.Error(), http.StatusInternalServerError)
   195  			return
   196  		}
   197  		payload := make(map[string]any)
   198  		if len(tasks) == 0 {
   199  			// avoid nil for the tasks field in json output.
   200  			payload["tasks"] = make([]*scheduledTask, 0)
   201  		} else {
   202  			payload["tasks"] = toScheduledTasks(tasks, pf)
   203  		}
   204  		payload["stats"] = toQueueStateSnapshot(qinfo)
   205  		writeResponseJSON(w, payload)
   206  	}
   207  }
   208  
   209  func newListRetryTasksHandlerFunc(inspector *asynq.Inspector, pf PayloadFormatter) http.HandlerFunc {
   210  	return func(w http.ResponseWriter, r *http.Request) {
   211  		vars := mux.Vars(r)
   212  		qname := vars["qname"]
   213  		pageSize, pageNum := getPageOptions(r)
   214  		tasks, err := inspector.ListRetryTasks(
   215  			qname, asynq.PageSize(pageSize), asynq.Page(pageNum))
   216  		if err != nil {
   217  			http.Error(w, err.Error(), http.StatusInternalServerError)
   218  			return
   219  		}
   220  		qinfo, err := inspector.GetQueueInfo(qname)
   221  		if err != nil {
   222  			http.Error(w, err.Error(), http.StatusInternalServerError)
   223  			return
   224  		}
   225  		payload := make(map[string]any)
   226  		if len(tasks) == 0 {
   227  			// avoid nil for the tasks field in json output.
   228  			payload["tasks"] = make([]*retryTask, 0)
   229  		} else {
   230  			payload["tasks"] = toRetryTasks(tasks, pf)
   231  		}
   232  		payload["stats"] = toQueueStateSnapshot(qinfo)
   233  		writeResponseJSON(w, payload)
   234  	}
   235  }
   236  
   237  func newListArchivedTasksHandlerFunc(inspector *asynq.Inspector, pf PayloadFormatter) http.HandlerFunc {
   238  	return func(w http.ResponseWriter, r *http.Request) {
   239  		vars := mux.Vars(r)
   240  		qname := vars["qname"]
   241  		pageSize, pageNum := getPageOptions(r)
   242  		tasks, err := inspector.ListArchivedTasks(
   243  			qname, asynq.PageSize(pageSize), asynq.Page(pageNum))
   244  		if err != nil {
   245  			http.Error(w, err.Error(), http.StatusInternalServerError)
   246  			return
   247  		}
   248  		qinfo, err := inspector.GetQueueInfo(qname)
   249  		if err != nil {
   250  			http.Error(w, err.Error(), http.StatusInternalServerError)
   251  			return
   252  		}
   253  		payload := make(map[string]any)
   254  		if len(tasks) == 0 {
   255  			// avoid nil for the tasks field in json output.
   256  			payload["tasks"] = make([]*archivedTask, 0)
   257  		} else {
   258  			payload["tasks"] = toArchivedTasks(tasks, pf)
   259  		}
   260  		payload["stats"] = toQueueStateSnapshot(qinfo)
   261  		writeResponseJSON(w, payload)
   262  	}
   263  }
   264  
   265  func newListCompletedTasksHandlerFunc(inspector *asynq.Inspector, pf PayloadFormatter, rf ResultFormatter) http.HandlerFunc {
   266  	return func(w http.ResponseWriter, r *http.Request) {
   267  		vars := mux.Vars(r)
   268  		qname := vars["qname"]
   269  		pageSize, pageNum := getPageOptions(r)
   270  		tasks, err := inspector.ListCompletedTasks(qname, asynq.PageSize(pageSize), asynq.Page(pageNum))
   271  		if err != nil {
   272  			http.Error(w, err.Error(), http.StatusInternalServerError)
   273  			return
   274  		}
   275  		qinfo, err := inspector.GetQueueInfo(qname)
   276  		if err != nil {
   277  			http.Error(w, err.Error(), http.StatusInternalServerError)
   278  			return
   279  		}
   280  		payload := make(map[string]any)
   281  		if len(tasks) == 0 {
   282  			// avoid nil for the tasks field in json output.
   283  			payload["tasks"] = make([]*completedTask, 0)
   284  		} else {
   285  			payload["tasks"] = toCompletedTasks(tasks, pf, rf)
   286  		}
   287  		payload["stats"] = toQueueStateSnapshot(qinfo)
   288  		writeResponseJSON(w, payload)
   289  	}
   290  }
   291  
   292  func newListAggregatingTasksHandlerFunc(inspector *asynq.Inspector, pf PayloadFormatter) http.HandlerFunc {
   293  	return func(w http.ResponseWriter, r *http.Request) {
   294  		vars := mux.Vars(r)
   295  		qname := vars["qname"]
   296  		gname := vars["gname"]
   297  		pageSize, pageNum := getPageOptions(r)
   298  		tasks, err := inspector.ListAggregatingTasks(
   299  			qname, gname, asynq.PageSize(pageSize), asynq.Page(pageNum))
   300  		if err != nil {
   301  			http.Error(w, err.Error(), http.StatusInternalServerError)
   302  			return
   303  		}
   304  		qinfo, err := inspector.GetQueueInfo(qname)
   305  		if err != nil {
   306  			http.Error(w, err.Error(), http.StatusInternalServerError)
   307  			return
   308  		}
   309  		groups, err := inspector.Groups(qname)
   310  		if err != nil {
   311  			http.Error(w, err.Error(), http.StatusInternalServerError)
   312  			return
   313  		}
   314  		payload := make(map[string]any)
   315  		if len(tasks) == 0 {
   316  			// avoid nil for the tasks field in json output.
   317  			payload["tasks"] = make([]*aggregatingTask, 0)
   318  		} else {
   319  			payload["tasks"] = toAggregatingTasks(tasks, pf)
   320  		}
   321  		payload["stats"] = toQueueStateSnapshot(qinfo)
   322  		payload["groups"] = toGroupInfos(groups)
   323  		writeResponseJSON(w, payload)
   324  	}
   325  }
   326  
   327  func newDeleteTaskHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   328  	return func(w http.ResponseWriter, r *http.Request) {
   329  		vars := mux.Vars(r)
   330  		qname, taskid := vars["qname"], vars["task_id"]
   331  		if qname == "" || taskid == "" {
   332  			http.Error(w, "route parameters should not be empty", http.StatusBadRequest)
   333  			return
   334  		}
   335  		if err := inspector.DeleteTask(qname, taskid); err != nil {
   336  			// TODO: Handle task not found error and return 404
   337  			http.Error(w, err.Error(), http.StatusInternalServerError)
   338  			return
   339  		}
   340  		w.WriteHeader(http.StatusNoContent)
   341  	}
   342  }
   343  
   344  func newRunTaskHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   345  	return func(w http.ResponseWriter, r *http.Request) {
   346  		vars := mux.Vars(r)
   347  		qname, taskid := vars["qname"], vars["task_id"]
   348  		if qname == "" || taskid == "" {
   349  			http.Error(w, "route parameters should not be empty", http.StatusBadRequest)
   350  			return
   351  		}
   352  		if err := inspector.RunTask(qname, taskid); err != nil {
   353  			// TODO: Handle task not found error and return 404
   354  			http.Error(w, err.Error(), http.StatusInternalServerError)
   355  			return
   356  		}
   357  		w.WriteHeader(http.StatusNoContent)
   358  	}
   359  }
   360  
   361  func newArchiveTaskHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   362  	return func(w http.ResponseWriter, r *http.Request) {
   363  		vars := mux.Vars(r)
   364  		qname, taskid := vars["qname"], vars["task_id"]
   365  		if qname == "" || taskid == "" {
   366  			http.Error(w, "route parameters should not be empty", http.StatusBadRequest)
   367  			return
   368  		}
   369  		if err := inspector.ArchiveTask(qname, taskid); err != nil {
   370  			// TODO: Handle task not found error and return 404
   371  			http.Error(w, err.Error(), http.StatusInternalServerError)
   372  			return
   373  		}
   374  		w.WriteHeader(http.StatusNoContent)
   375  	}
   376  }
   377  
   378  type deleteAllTasksResponse struct {
   379  	// Number of tasks deleted.
   380  	Deleted int `json:"deleted"`
   381  }
   382  
   383  func newDeleteAllPendingTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   384  	return func(w http.ResponseWriter, r *http.Request) {
   385  		qname := mux.Vars(r)["qname"]
   386  		n, err := inspector.DeleteAllPendingTasks(qname)
   387  		if err != nil {
   388  			http.Error(w, err.Error(), http.StatusInternalServerError)
   389  			return
   390  		}
   391  		writeResponseJSON(w, deleteAllTasksResponse{n})
   392  	}
   393  }
   394  
   395  func newDeleteAllAggregatingTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   396  	return func(w http.ResponseWriter, r *http.Request) {
   397  		vars := mux.Vars(r)
   398  		qname, gname := vars["qname"], vars["gname"]
   399  		n, err := inspector.DeleteAllAggregatingTasks(qname, gname)
   400  		if err != nil {
   401  			http.Error(w, err.Error(), http.StatusInternalServerError)
   402  			return
   403  		}
   404  		writeResponseJSON(w, deleteAllTasksResponse{n})
   405  	}
   406  }
   407  
   408  func newDeleteAllScheduledTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   409  	return func(w http.ResponseWriter, r *http.Request) {
   410  		qname := mux.Vars(r)["qname"]
   411  		n, err := inspector.DeleteAllScheduledTasks(qname)
   412  		if err != nil {
   413  			http.Error(w, err.Error(), http.StatusInternalServerError)
   414  			return
   415  		}
   416  		writeResponseJSON(w, deleteAllTasksResponse{n})
   417  	}
   418  }
   419  
   420  func newDeleteAllRetryTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   421  	return func(w http.ResponseWriter, r *http.Request) {
   422  		qname := mux.Vars(r)["qname"]
   423  		n, err := inspector.DeleteAllRetryTasks(qname)
   424  		if err != nil {
   425  			http.Error(w, err.Error(), http.StatusInternalServerError)
   426  			return
   427  		}
   428  		writeResponseJSON(w, deleteAllTasksResponse{n})
   429  	}
   430  }
   431  
   432  func newDeleteAllArchivedTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   433  	return func(w http.ResponseWriter, r *http.Request) {
   434  		qname := mux.Vars(r)["qname"]
   435  		n, err := inspector.DeleteAllArchivedTasks(qname)
   436  		if err != nil {
   437  			http.Error(w, err.Error(), http.StatusInternalServerError)
   438  			return
   439  		}
   440  		writeResponseJSON(w, deleteAllTasksResponse{n})
   441  	}
   442  }
   443  
   444  func newDeleteAllCompletedTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   445  	return func(w http.ResponseWriter, r *http.Request) {
   446  		qname := mux.Vars(r)["qname"]
   447  		n, err := inspector.DeleteAllCompletedTasks(qname)
   448  		if err != nil {
   449  			http.Error(w, err.Error(), http.StatusInternalServerError)
   450  			return
   451  		}
   452  		writeResponseJSON(w, deleteAllTasksResponse{n})
   453  	}
   454  }
   455  
   456  type runAllTasksResponse struct {
   457  	// Number of tasks scheduled to run.
   458  	Scheduled int `json:"scheduled"`
   459  }
   460  
   461  func newRunAllScheduledTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   462  	return func(w http.ResponseWriter, r *http.Request) {
   463  		qname := mux.Vars(r)["qname"]
   464  		n, err := inspector.RunAllScheduledTasks(qname)
   465  		if err != nil {
   466  			http.Error(w, err.Error(), http.StatusInternalServerError)
   467  			return
   468  		}
   469  		writeResponseJSON(w, runAllTasksResponse{n})
   470  	}
   471  }
   472  
   473  func newRunAllRetryTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   474  	return func(w http.ResponseWriter, r *http.Request) {
   475  		qname := mux.Vars(r)["qname"]
   476  		n, err := inspector.RunAllRetryTasks(qname)
   477  		if err != nil {
   478  			http.Error(w, err.Error(), http.StatusInternalServerError)
   479  			return
   480  		}
   481  		writeResponseJSON(w, runAllTasksResponse{n})
   482  	}
   483  }
   484  
   485  func newRunAllArchivedTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   486  	return func(w http.ResponseWriter, r *http.Request) {
   487  		qname := mux.Vars(r)["qname"]
   488  		n, err := inspector.RunAllArchivedTasks(qname)
   489  		if err != nil {
   490  			http.Error(w, err.Error(), http.StatusInternalServerError)
   491  			return
   492  		}
   493  		writeResponseJSON(w, runAllTasksResponse{n})
   494  	}
   495  }
   496  
   497  func newRunAllAggregatingTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   498  	return func(w http.ResponseWriter, r *http.Request) {
   499  		vars := mux.Vars(r)
   500  		qname, gname := vars["qname"], vars["gname"]
   501  		n, err := inspector.RunAllAggregatingTasks(qname, gname)
   502  		if err != nil {
   503  			http.Error(w, err.Error(), http.StatusInternalServerError)
   504  			return
   505  		}
   506  		writeResponseJSON(w, runAllTasksResponse{n})
   507  	}
   508  }
   509  
   510  type archiveAllTasksResponse struct {
   511  	// Number of tasks archived.
   512  	Archived int `json:"archived"`
   513  }
   514  
   515  func writeResponseJSON(w http.ResponseWriter, resp any) {
   516  	if err := json.NewEncoder(w).Encode(resp); err != nil {
   517  		http.Error(w, err.Error(), http.StatusInternalServerError)
   518  	}
   519  }
   520  
   521  func newArchiveAllPendingTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   522  	return func(w http.ResponseWriter, r *http.Request) {
   523  		qname := mux.Vars(r)["qname"]
   524  		n, err := inspector.ArchiveAllPendingTasks(qname)
   525  		if err != nil {
   526  			http.Error(w, err.Error(), http.StatusInternalServerError)
   527  			return
   528  		}
   529  		writeResponseJSON(w, archiveAllTasksResponse{n})
   530  	}
   531  }
   532  
   533  func newArchiveAllAggregatingTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   534  	return func(w http.ResponseWriter, r *http.Request) {
   535  		vars := mux.Vars(r)
   536  		qname, gname := vars["qname"], vars["gname"]
   537  		n, err := inspector.ArchiveAllAggregatingTasks(qname, gname)
   538  		if err != nil {
   539  			http.Error(w, err.Error(), http.StatusInternalServerError)
   540  			return
   541  		}
   542  		writeResponseJSON(w, archiveAllTasksResponse{n})
   543  	}
   544  }
   545  
   546  func newArchiveAllScheduledTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   547  	return func(w http.ResponseWriter, r *http.Request) {
   548  		qname := mux.Vars(r)["qname"]
   549  		n, err := inspector.ArchiveAllScheduledTasks(qname)
   550  		if err != nil {
   551  			http.Error(w, err.Error(), http.StatusInternalServerError)
   552  			return
   553  		}
   554  		writeResponseJSON(w, archiveAllTasksResponse{n})
   555  	}
   556  }
   557  
   558  func newArchiveAllRetryTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   559  	return func(w http.ResponseWriter, r *http.Request) {
   560  		qname := mux.Vars(r)["qname"]
   561  		n, err := inspector.ArchiveAllRetryTasks(qname)
   562  		if err != nil {
   563  			http.Error(w, err.Error(), http.StatusInternalServerError)
   564  			return
   565  		}
   566  		writeResponseJSON(w, archiveAllTasksResponse{n})
   567  	}
   568  }
   569  
   570  // request body used for all batch delete tasks endpoints.
   571  type batchDeleteTasksRequest struct {
   572  	TaskIDs []string `json:"task_ids"`
   573  }
   574  
   575  // Note: Redis does not have any rollback mechanism, so it's possible
   576  // to have partial success when doing a batch operation.
   577  // For this reason this response contains a list of succeeded ids
   578  // and a list of failed ids.
   579  type batchDeleteTasksResponse struct {
   580  	// task ids that were successfully deleted.
   581  	DeletedIDs []string `json:"deleted_ids"`
   582  
   583  	// task ids that were not deleted.
   584  	FailedIDs []string `json:"failed_ids"`
   585  }
   586  
   587  // Maximum request body size in bytes.
   588  // Allow up to 1MB in size.
   589  const maxRequestBodySize = 1000000
   590  
   591  func newBatchDeleteTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   592  	return func(w http.ResponseWriter, r *http.Request) {
   593  		r.Body = http.MaxBytesReader(w, r.Body, maxRequestBodySize)
   594  		dec := json.NewDecoder(r.Body)
   595  		dec.DisallowUnknownFields()
   596  
   597  		var req batchDeleteTasksRequest
   598  		if err := dec.Decode(&req); err != nil {
   599  			http.Error(w, err.Error(), http.StatusBadRequest)
   600  			return
   601  		}
   602  
   603  		qname := mux.Vars(r)["qname"]
   604  		resp := batchDeleteTasksResponse{
   605  			// avoid null in the json response
   606  			DeletedIDs: make([]string, 0),
   607  			FailedIDs:  make([]string, 0),
   608  		}
   609  		for _, taskid := range req.TaskIDs {
   610  			if err := inspector.DeleteTask(qname, taskid); err != nil {
   611  				log.Printf("error: could not delete task with id %q: %v", taskid, err)
   612  				resp.FailedIDs = append(resp.FailedIDs, taskid)
   613  			} else {
   614  				resp.DeletedIDs = append(resp.DeletedIDs, taskid)
   615  			}
   616  		}
   617  		writeResponseJSON(w, resp)
   618  	}
   619  }
   620  
   621  type batchRunTasksRequest struct {
   622  	TaskIDs []string `json:"task_ids"`
   623  }
   624  
   625  type batchRunTasksResponse struct {
   626  	// task ids that were successfully moved to the pending state.
   627  	PendingIDs []string `json:"pending_ids"`
   628  	// task ids that were not able to move to the pending state.
   629  	ErrorIDs []string `json:"error_ids"`
   630  }
   631  
   632  func newBatchRunTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   633  	return func(w http.ResponseWriter, r *http.Request) {
   634  		r.Body = http.MaxBytesReader(w, r.Body, maxRequestBodySize)
   635  		dec := json.NewDecoder(r.Body)
   636  		dec.DisallowUnknownFields()
   637  
   638  		var req batchRunTasksRequest
   639  		if err := dec.Decode(&req); err != nil {
   640  			http.Error(w, err.Error(), http.StatusBadRequest)
   641  			return
   642  		}
   643  
   644  		qname := mux.Vars(r)["qname"]
   645  		resp := batchRunTasksResponse{
   646  			// avoid null in the json response
   647  			PendingIDs: make([]string, 0),
   648  			ErrorIDs:   make([]string, 0),
   649  		}
   650  		for _, taskid := range req.TaskIDs {
   651  			if err := inspector.RunTask(qname, taskid); err != nil {
   652  				log.Printf("error: could not run task with id %q: %v", taskid, err)
   653  				resp.ErrorIDs = append(resp.ErrorIDs, taskid)
   654  			} else {
   655  				resp.PendingIDs = append(resp.PendingIDs, taskid)
   656  			}
   657  		}
   658  		writeResponseJSON(w, resp)
   659  	}
   660  }
   661  
   662  type batchArchiveTasksRequest struct {
   663  	TaskIDs []string `json:"task_ids"`
   664  }
   665  
   666  type batchArchiveTasksResponse struct {
   667  	// task ids that were successfully moved to the archived state.
   668  	ArchivedIDs []string `json:"archived_ids"`
   669  	// task ids that were not able to move to the archived state.
   670  	ErrorIDs []string `json:"error_ids"`
   671  }
   672  
   673  func newBatchArchiveTasksHandlerFunc(inspector *asynq.Inspector) http.HandlerFunc {
   674  	return func(w http.ResponseWriter, r *http.Request) {
   675  		r.Body = http.MaxBytesReader(w, r.Body, maxRequestBodySize)
   676  		dec := json.NewDecoder(r.Body)
   677  		dec.DisallowUnknownFields()
   678  
   679  		var req batchArchiveTasksRequest
   680  		if err := dec.Decode(&req); err != nil {
   681  			http.Error(w, err.Error(), http.StatusBadRequest)
   682  			return
   683  		}
   684  
   685  		qname := mux.Vars(r)["qname"]
   686  		resp := batchArchiveTasksResponse{
   687  			// avoid null in the json response
   688  			ArchivedIDs: make([]string, 0),
   689  			ErrorIDs:    make([]string, 0),
   690  		}
   691  		for _, taskid := range req.TaskIDs {
   692  			if err := inspector.ArchiveTask(qname, taskid); err != nil {
   693  				log.Printf("error: could not archive task with id %q: %v", taskid, err)
   694  				resp.ErrorIDs = append(resp.ErrorIDs, taskid)
   695  			} else {
   696  				resp.ArchivedIDs = append(resp.ArchivedIDs, taskid)
   697  			}
   698  		}
   699  		writeResponseJSON(w, resp)
   700  	}
   701  }
   702  
   703  // getPageOptions read page size and number from the request url if set,
   704  // otherwise it returns the default value.
   705  func getPageOptions(r *http.Request) (pageSize, pageNum int) {
   706  	pageSize = 20 // default page size
   707  	pageNum = 1   // default page num
   708  	q := r.URL.Query()
   709  	if s := q.Get("size"); s != "" {
   710  		if n, err := strconv.Atoi(s); err == nil {
   711  			pageSize = n
   712  		}
   713  	}
   714  	if s := q.Get("page"); s != "" {
   715  		if n, err := strconv.Atoi(s); err == nil {
   716  			pageNum = n
   717  		}
   718  	}
   719  	return pageSize, pageNum
   720  }
   721  
   722  func newGetTaskHandlerFunc(inspector *asynq.Inspector, pf PayloadFormatter, rf ResultFormatter) http.HandlerFunc {
   723  	return func(w http.ResponseWriter, r *http.Request) {
   724  		vars := mux.Vars(r)
   725  		qname, taskid := vars["qname"], vars["task_id"]
   726  		if qname == "" {
   727  			http.Error(w, "queue name cannot be empty", http.StatusBadRequest)
   728  			return
   729  		}
   730  		if taskid == "" {
   731  			http.Error(w, "task_id cannot be empty", http.StatusBadRequest)
   732  			return
   733  		}
   734  
   735  		info, err := inspector.GetTaskInfo(qname, taskid)
   736  		switch {
   737  		case errors.Is(err, asynq.ErrQueueNotFound), errors.Is(err, asynq.ErrTaskNotFound):
   738  			http.Error(w, strings.TrimPrefix(err.Error(), "asynq: "), http.StatusNotFound)
   739  			return
   740  		case err != nil:
   741  			http.Error(w, strings.TrimPrefix(err.Error(), "asynq: "), http.StatusInternalServerError)
   742  			return
   743  		}
   744  
   745  		writeResponseJSON(w, toTaskInfo(info, pf, rf))
   746  	}
   747  }