// :WEBAPI: // { // "url": "{schema}://{host}/api/v1/tasks/{taskid}/subtasks/{subtaskid}", // "method": "DELETE", // "arguments": [ // {"name": "taskid", "type": "integer", "description": "task number"}, // {"name": "subtaskid", "type": "integer", "description": "subtask number"} // ], // "description": "Removes subtask" // } func SubtaskDeleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { p, ok := ctx.Value("http.request.query.params").(*url.Values) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain params from context") return } st, ok := ctx.Value("app.database").(db.Session) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain database from context") return } subtaskID := subtask.MakeID(util.ToInt64(p.Get("task")), util.ToInt64(p.Get("subtask"))) if err := subtask.Delete(st, subtaskID); err != nil { if db.IsNotFound(err) { ahttp.HTTPResponse(w, http.StatusNotFound, "Not found") } else { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to delete: %v", err) } return } ahttp.HTTPResponse(w, http.StatusOK, "OK") }
func apiGet(ctx context.Context, w http.ResponseWriter, r *http.Request, query Query) { st, ok := ctx.Value("app.database").(db.Session) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain database from context") return } col, err := st.Coll(query.CollName) if err != nil { ahttp.HTTPResponse(w, http.StatusInternalServerError, "%v", err) return } collQuery := col.Find(query.Pattern) doc, err := query.One(collQuery) if err != nil { if db.IsNotFound(err) { ahttp.HTTPResponse(w, http.StatusNotFound, "Not found") } else { ahttp.HTTPResponse(w, http.StatusInternalServerError, "%v", err) } return } msg, err := json.Marshal(doc) if err != nil { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to marshal document") return } w.Write([]byte(`{"result":`)) w.Write(msg) w.Write([]byte(`}`)) }
// :WEBAPI: // { // "url": "{schema}://{host}/api/v1/acl", // "method": "GET", // "description": "Returns list of supported repositories" // } func AclReposListHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { if cfg, ok := ctx.Value("app.config").(*config.Config); ok { msg, err := json.Marshal(cfg.Builder.Repos) if err != nil { ahttp.HTTPResponse(w, http.StatusBadRequest, "Unable to marshal json: %v", err) return } w.Write(msg) return } ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain config from context") }
// :WEBAPI: // { // "url": "{schema}://{host}/api/v1/tasks", // "method": "GET", // "description": "Returns list of tasks", // "parameters": [ // {"name": "state", "type": "string", "description": "shows tasks with specified state", "default": "NaN"}, // {"name": "owner", "type": "string", "description": "shows tasks with specified owner", "default": "NaN"}, // {"name": "limit", "type": "number", "description": "shows only specified number of retults", "default": "1000"} // ] // } func TaskListHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { p, ok := ctx.Value("http.request.query.params").(*url.Values) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain params from context") return } q := db.QueryDoc{} if p.Get("owner") != "" { q["owner"] = p.Get("owner") } if p.Get("repo") != "" { q["repo"] = p.Get("repo") } if p.Get("state") != "" { q["state"] = p.Get("state") } apiSearch(ctx, w, r, []Query{ Query{ CollName: task.CollName, Sort: []string{"taskid"}, Pattern: q, Iterator: func(iter db.Iter) interface{} { t := task.New() if !iter.Next(t) { return nil } return t }, }, }) }
// :WEBAPI: // { // "url": "{schema}://{host}/api/v1/search", // "method": "GET", // "parameters": [ // {"name": "prefix", "type": "string", "description": "filter objects by prefix", "default": "NaN"}, // {"name": "limit", "type": "number", "description": "shows only specified number of retults", "default": "1000"} // ], // "description": "Returns list of tasks and subtasks" // } func SearchHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { p, ok := ctx.Value("http.request.query.params").(*url.Values) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain params from context") return } apiSearch(ctx, w, r, []Query{ Query{ CollName: task.CollName, Pattern: db.QueryDoc{"search.key": db.QueryDoc{"$regex": "^" + p.Get("prefix")}}, Sort: []string{"-taskid"}, Iterator: func(iter db.Iter) interface{} { t := task.New() if !iter.Next(t) { return nil } return t }, }, Query{ CollName: subtask.CollName, Pattern: db.QueryDoc{"search.key": db.QueryDoc{"$regex": "^" + p.Get("prefix")}}, Sort: []string{"-taskid"}, Iterator: func(iter db.Iter) interface{} { t := subtask.New() if !iter.Next(t) { return nil } return t }, }, }) }
// :WEBAPI: // { // "url": "{schema}://{host}/api/v1/acl/{repo}/{type}/{name}", // "method": "GET", // "arguments": [ // {"name": "repo", "type": "string", "description": "repository name"}, // {"name": "type", "type": "string", "description": "type of object, can be 'package' or 'group'"}, // {"name": "name", "type": "string", "description": "name of object"} // ], // "description": "Shows the ACL for the specified name in the repository" // } func AclGetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { p, ok := ctx.Value("http.request.query.params").(*url.Values) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain params from context") return } apiGet(ctx, w, r, Query{ CollName: "acl_" + p.Get("type"), Pattern: db.QueryDoc{ "repo": p.Get("repo"), "name": p.Get("name"), }, One: func(query db.Query) (interface{}, error) { var err error t := &acl.ACL{} if p.Get("type") == "groups" { if p.Get("name") == "nobody" || p.Get("name") == "everybody" { t.Name = p.Get("name") t.Repo = p.Get("repo") t.Members = make([]acl.Member, 0) return t, err } } err = query.One(t) return t, err }, }) }
// :WEBAPI: // { // "url": "{schema}://{host}/api/v1/tasks/{taskid}/subtasks", // "method": "POST", // "arguments": [ // {"name": "taskid", "type": "integer", "description": "task number"} // ], // "description": "Creates new subtask for specified task" // } func SubtaskCreateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { p, ok := ctx.Value("http.request.query.params").(*url.Values) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain params from context") return } msg, err := ioutil.ReadAll(r.Body) if err != nil { ahttp.HTTPResponse(w, http.StatusBadRequest, "Unable to read body: %s", err) return } logger.GetHTTPEntry(ctx).WithFields(nil).Debugf("SubtaskCreateHandler: Request body: %s", string(msg)) t := subtask.New() if err = json.Unmarshal(msg, t); err != nil { ahttp.HTTPResponse(w, http.StatusBadRequest, "Invalid JSON: %s", err) return } if !t.Owner.IsDefined() { ahttp.HTTPResponse(w, http.StatusBadRequest, "owner: mandatory field is not specified") return } if !t.Type.IsDefined() { t.Type.Set("unknown") } if !t.Status.IsDefined() { t.Status.Set("active") } t.TaskID.Set(util.ToInt64(p.Get("task"))) t.TimeCreate.Set(time.Now().Unix()) logger.GetHTTPEntry(ctx).WithFields(nil).Debugf("SubtaskCreateHandler: SubTask: %+v", t) if !writeSubTask(ctx, w, t) { return } ahttp.HTTPResponse(w, http.StatusOK, "OK") }
// :WEBAPI: // { // "url": "{schema}://{host}/api/v1/tasks/{taskid}", // "method": "POST", // "arguments": [ // {"name": "taskid", "type": "integer", "description": "task number"} // ], // "description": "Updates existing task" // } func TaskUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { p, ok := ctx.Value("http.request.query.params").(*url.Values) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain params from context") return } st, ok := ctx.Value("app.database").(db.Session) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain database from context") return } msg, err := ioutil.ReadAll(r.Body) if err != nil { ahttp.HTTPResponse(w, http.StatusBadRequest, "Unable to read body: %s", err) return } logger.GetHTTPEntry(ctx).WithFields(nil).Infof("TaskUpdateHandler: Request body: %s", string(msg)) ev := task.NewTaskEvent() if err = json.Unmarshal(msg, ev); err != nil { ahttp.HTTPResponse(w, http.StatusBadRequest, "Invalid JSON: %s", err) return } taskID := task.MakeID(util.ToInt64(p.Get("task"))) t, err := task.Read(st, taskID) if err != nil { if db.IsNotFound(err) { ahttp.HTTPResponse(w, http.StatusNotFound, "Not found") } else { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to read: %v", err) } return } t.TaskID.Set(util.ToInt64(p.Get("task"))) if !t.TimeCreate.IsDefined() { t.TimeCreate.Set(time.Now().Unix()) } fillTask(t, ev) logger.GetHTTPEntry(ctx).WithFields(nil).Infof("TaskUpdateHandler: Task: %+v", t) if !writeTask(ctx, w, t) { return } ahttp.HTTPResponse(w, http.StatusOK, "OK") }
func FileHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { if cfg, ok := ctx.Value("app.config").(*config.Config); ok { path := r.URL.Path if _, err := os.Stat(cfg.Content.Path + path); err != nil { path = "/index.html" } http.ServeFile(w, r, cfg.Content.Path+path) return } ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain config from context") InternalServerErrorHandler(ctx, w, r) }
// :WEBAPI: // { // "url": "{schema}://{host}/api/v1/tasks/{taskid}/subtasks/{subtaskid}", // "method": "POST", // "arguments": [ // {"name": "taskid", "type": "integer", "description": "task number"}, // {"name": "subtaskid", "type": "integer", "description": "subtask number"} // ], // "description": "Updates subtask in task" // } func SubtaskUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { p, ok := ctx.Value("http.request.query.params").(*url.Values) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain params from context") return } st, ok := ctx.Value("app.database").(db.Session) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain database from context") return } subtaskID := subtask.MakeID(util.ToInt64(p.Get("task")), util.ToInt64(p.Get("subtask"))) t, err := subtask.Read(st, subtaskID) if err != nil { if db.IsNotFound(err) { ahttp.HTTPResponse(w, http.StatusNotFound, "Not found") } else { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to read: %v", err) } return } t.TaskID.Set(util.ToInt64(p.Get("task"))) t.SubTaskID.Set(util.ToInt64(p.Get("subtask"))) t.TaskID.Readonly(true) t.SubTaskID.Readonly(true) t.TimeCreate.Readonly(true) msg, err := ioutil.ReadAll(r.Body) if err != nil { ahttp.HTTPResponse(w, http.StatusBadRequest, "Unable to read body: %s", err) return } logger.GetHTTPEntry(ctx).WithFields(nil).Debugf("SubtaskUpdateHandler: Request body: %s", string(msg)) if err = json.Unmarshal(msg, t); err != nil { ahttp.HTTPResponse(w, http.StatusBadRequest, "Invalid JSON: %s", err) return } logger.GetHTTPEntry(ctx).WithFields(nil).Debugf("SubtaskUpdateHandler: SubTask: %+v", t) if !writeSubTask(ctx, w, t) { return } ahttp.HTTPResponse(w, http.StatusOK, "OK") }
func Handler(ctx context.Context, w http.ResponseWriter, r *http.Request) { info, ok := ctx.Value("http.endpoints").(*EndpointsInfo) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain API information from context") InternalServerErrorHandler(ctx, w, r) return } p := r.URL.Query() for _, a := range info.Endpoints { match := a.Regexp.FindStringSubmatch(r.URL.Path) if match == nil { continue } for i, name := range a.Regexp.SubexpNames() { if i == 0 { continue } p.Set(name, match[i]) } ctx = context.WithValue(ctx, "http.request.query.params", &p) var reqHandler ahttp.Handler if v, ok := a.Handlers[r.Method]; ok { reqHandler = v if a.NeedDBHandler { reqHandler = db.Handler(reqHandler) } if a.NeedJSONHandler { reqHandler = jsonresponse.Handler(reqHandler) } } else { reqHandler = NotAllowedHandler } reqHandler(ctx, w, r) return } // Never should be here NotFoundHandler(ctx, w, r) }
func TestInternalServerErrorHandler(t *testing.T) { w := testresponse.NewResponseWriter() r := &http.Request{} ctx := context.Background() ahttp.HTTPResponse(w, http.StatusInternalServerError, "Error!") InternalServerErrorHandler(ctx, w, r) expect := "Status: 500\n\nInternal server error\n" result := w.(*testresponse.ResponseWriter).String() if expect != result { t.Errorf("Unexpected result: %q", result) } }
func TestInternalServerErrorHandlerJSON(t *testing.T) { p := testresponse.NewResponseWriter() w := ahttp.NewResponseWriter(p) r := &http.Request{} ctx := context.Background() ahttp.HTTPResponse(w, http.StatusInternalServerError, "Error!") InternalServerErrorHandler(ctx, w, r) expect := "Status: 500\n\n{\"data\":{\"status\":500,\"title\":\"Internal Server Error\",\"detail\":\"Error!\"},\"status\":\"error\"}\n" result := p.(*testresponse.ResponseWriter).String() if expect != result { t.Errorf("Unexpected result: %q", result) } }
// :WEBAPI: // { // "url": "{schema}://{host}/api/v1/tasks/{taskid}/subtasks/{subtaskid}", // "method": "GET", // "arguments": [ // {"name": "taskid", "type": "integer", "description": "task number"}, // {"name": "subtaskid", "type": "integer", "description": "subtask number"} // ], // "description": "Creates new subtask for specified task" // } func SubtaskGetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { p, ok := ctx.Value("http.request.query.params").(*url.Values) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain params from context") return } apiGet(ctx, w, r, Query{ CollName: subtask.CollName, Pattern: subtask.MakeID(util.ToInt64(p.Get("task")), util.ToInt64(p.Get("subtask"))), One: func(query db.Query) (interface{}, error) { t := subtask.New() err := query.One(t) return t, err }, }) }
// :WEBAPI: // { // "url": "{schema}://{host}/api/v1/tasks", // "method": "POST", // "description": "Creates new task" // } func TaskCreateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { cfg, ok := ctx.Value("app.config").(*config.Config) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain config from context") return } msg, err := ioutil.ReadAll(r.Body) if err != nil { ahttp.HTTPResponse(w, http.StatusBadRequest, "Unable to read body: %s", err) return } logger.GetHTTPEntry(ctx).WithFields(nil).Infof("TaskCreateHandler: Request body: %s", string(msg)) ev := task.NewTaskEvent() if err = json.Unmarshal(msg, ev); err != nil { ahttp.HTTPResponse(w, http.StatusBadRequest, "Invalid JSON: %s", err) return } t := task.New() fillTask(t, ev) t.TimeCreate.Set(time.Now().Unix()) logger.GetHTTPEntry(ctx).WithFields(nil).Infof("TaskCreateHandler: Task: %+v", t) if v, ok := t.Repo.Get(); ok { if !util.InSliceString(v, cfg.Builder.Repos) { ahttp.HTTPResponse(w, http.StatusBadRequest, "Unknown repo") return } } else { ahttp.HTTPResponse(w, http.StatusBadRequest, "repo: mandatory field is not specified") return } if !t.Owner.IsDefined() { ahttp.HTTPResponse(w, http.StatusBadRequest, "owner: mandatory field is not specified") return } if !writeTask(ctx, w, t) { return } ahttp.HTTPResponse(w, http.StatusOK, "OK") }
// :WEBAPI: // { // "url": "{schema}://{host}/api/v1/tasks/{taskid}/subtasks", // "method": "GET", // "arguments": [ // {"name": "taskid", "type": "integer", "description": "task number"} // ], // "parameters": [ // {"name": "limit", "type": "number", "description": "shows only specified number of retults", "default": "1000"} // ], // "description": "Returns information about specified subtask" // } func SubtaskListHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { p, ok := ctx.Value("http.request.query.params").(*url.Values) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain params from context") return } apiSearch(ctx, w, r, []Query{ Query{ CollName: subtask.CollName, Sort: []string{"subtaskid"}, Pattern: db.QueryDoc{"taskid": util.ToInt64(p.Get("task"))}, Iterator: func(iter db.Iter) interface{} { t := subtask.New() if !iter.Next(t) { return nil } return t }, }, }) }
// :WEBAPI: // { // "url": "{schema}://{host}/api/v1/acl/{repo}/search/{type}", // "method": "GET", // "arguments": [ // {"name": "repo", "type": "string", "description": "repository name"}, // {"name": "type", "type": "string", "description": "type of object, can be 'package' or 'group'"} // ], // "description": "Returns list of all objects" // } // :WEBAPI: // { // "url": "{schema}://{host}/api/v1/acl/{repo}/{type}", // "method": "GET", // "arguments": [ // {"name": "repo", "type": "string", "description": "repository name"}, // {"name": "type", "type": "string", "description": "type of object, can be 'package' or 'group'"} // ], // "description": "Returns list of all objects" // } func AclListHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { p, ok := ctx.Value("http.request.query.params").(*url.Values) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain params from context") return } query := db.QueryDoc{ "repo": p.Get("repo"), } if p.Get("prefix") == "" { if p.Get("name") != "" { query["name"] = p.Get("name") } if p.Get("member") != "" { query["members.name"] = p.Get("member") } } else { query["name"] = db.QueryDoc{"$regex": "^" + p.Get("prefix")} } apiSearch(ctx, w, r, []Query{ Query{ CollName: "acl_" + p.Get("type"), Sort: []string{"name"}, Pattern: query, Iterator: func(iter db.Iter) interface{} { t := &acl.ACL{} if !iter.Next(t) { return nil } return t }, }, }) }
func writeTask(ctx context.Context, w http.ResponseWriter, t *task.Task) bool { st, ok := ctx.Value("app.database").(db.Session) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain database from context") return false } cfg, ok := ctx.Value("app.config").(*config.Config) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain config from context") return false } if v, ok := t.TaskID.Get(); ok { if v < int64(1) { ahttp.HTTPResponse(w, http.StatusBadRequest, "taskid must be greater than zero") return false } } else { ahttp.HTTPResponse(w, http.StatusBadRequest, "taskid: mandatory field is not specified") return false } if v, ok := t.State.Get(); ok { if !util.InSliceString(v, cfg.Builder.TaskStates) { ahttp.HTTPResponse(w, http.StatusBadRequest, "Unknown state") return false } } if err := task.Write(st, t); err != nil { if db.IsDup(err) { ahttp.HTTPResponse(w, http.StatusBadRequest, "Already exists") } else { ahttp.HTTPResponse(w, http.StatusInternalServerError, "%+v", err) } return false } return true }
func apiSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, q []Query) { p, ok := ctx.Value("http.request.query.params").(*url.Values) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain params from context") return } st, ok := ctx.Value("app.database").(db.Session) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain database from context") return } limit := util.ToInt32(p.Get("limit")) if limit == 0 { limit = 1000 } delim := false w.Write([]byte(`{"result":[`)) for _, query := range q { if !ahttp.IsAlive(w) { return } if limit == 0 { break } col, err := st.Coll(query.CollName) if err != nil { ahttp.HTTPResponse(w, http.StatusInternalServerError, "%v", err) return } collQuery := col.Find(query.Pattern).Limit(int(limit)) if len(query.Sort) > 0 { collQuery = collQuery.Sort(query.Sort...) } iter := collQuery.Iter() for { if !ahttp.IsAlive(w) { return } if limit == 0 { break } doc := query.Iterator(iter) if doc == nil { break } msg, err := json.Marshal(doc) if err != nil { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to marshal document") return } if delim { w.Write([]byte(`,`)) } w.Write(msg) limit-- delim = true } if err := iter.Close(); err != nil { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Error iterating: %+v", err) return } } w.Write([]byte(`],"query":[`)) delim = false for _, query := range q { msg, err := json.Marshal(query.Pattern) if err != nil { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to marshal pattern") return } if delim { w.Write([]byte(`,`)) } w.Write(msg) delim = true } w.Write([]byte(`]}`)) }
func StatisticQueueHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { st, ok := ctx.Value("app.database").(db.Session) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain database from context") return } cfg, ok := ctx.Value("app.config").(*config.Config) if !ok { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to obtain config from context") return } ans := make(map[string]map[string]int64) for _, repo := range cfg.Builder.Repos { ans[repo] = make(map[string]int64) for _, state := range taskStates { ans[repo][state] = 0 } } q := db.QueryDoc{ "state": db.QueryDoc{"$in": []string{"new", "awaiting", "postponed", "building", "pending", "committing"}}, } col, err := st.Coll(task.CollName) if err != nil { ahttp.HTTPResponse(w, http.StatusInternalServerError, "%v", err) return } iter := col.Find(q).Iter() for { if !ahttp.IsAlive(w) { return } t := task.New() if !iter.Next(t) { break } state, ok := t.State.Get() if !ok { continue } repo, ok := t.Repo.Get() if !ok { continue } ans[repo][state] += int64(1) } if err := iter.Close(); err != nil { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Error iterating: %+v", err) return } msg, err := json.Marshal(ans) if err != nil { ahttp.HTTPResponse(w, http.StatusInternalServerError, "Unable to marshal json: %v", err) return } w.Write([]byte(`{"result":`)) w.Write(msg) w.Write([]byte(`}`)) }
func NotAllowedHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { ahttp.HTTPResponse(w, http.StatusMethodNotAllowed, "Method Not Allowed") JSONHandler(ctx, w, r) }
func NotFoundHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { ahttp.HTTPResponse(w, http.StatusNotFound, "Page not found") JSONHandler(ctx, w, r) }
func PingHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { ahttp.HTTPResponse(w, http.StatusOK, "OK") w.Write([]byte("")) }