{private,satellite}: add Typescript generation to REST API generation

github issue: https://github.com/storj/storj/issues/4984

Change-Id: Id3473a6642f7a4af80edda25a6242559efaf69e9
This commit is contained in:
Cameron 2022-08-09 17:38:01 -04:00 committed by Cameron
parent c255d6949f
commit fa23e55654
5 changed files with 576 additions and 100 deletions

View File

@ -13,6 +13,7 @@ type Endpoint struct {
Name string
Description string
MethodName string
RequestName string
NoCookieAuth bool
NoAPIAuth bool
Request interface{}

View File

@ -39,7 +39,7 @@ func (a *API) MustWriteGo(path string) {
func (a *API) generateGo() ([]byte, error) {
var result string
p := func(format string, a ...interface{}) {
pf := func(format string, a ...interface{}) {
result += fmt.Sprintf(format+"\n", a...)
}
@ -94,15 +94,15 @@ func (a *API) generateGo() ([]byte, error) {
for _, group := range a.EndpointGroups {
i("github.com/zeebo/errs")
p("var Err%sAPI = errs.Class(\"%s %s api\")", cases.Title(language.Und).String(group.Prefix), a.PackageName, group.Prefix)
pf("var Err%sAPI = errs.Class(\"%s %s api\")", cases.Title(language.Und).String(group.Prefix), a.PackageName, group.Prefix)
}
p("")
pf("")
params := make(map[*fullEndpoint][]Param)
for _, group := range a.EndpointGroups {
p("type %sService interface {", group.Name)
pf("type %sService interface {", group.Name)
for _, e := range group.endpoints {
params[e] = append(e.QueryParams, e.PathParams...)
@ -121,82 +121,82 @@ func (a *API) generateGo() ([]byte, error) {
if responseType == getElementaryType(responseType) {
returnParam = "*" + returnParam
}
p("%s(context.Context, "+paramStr+") (%s, api.HTTPError)", e.MethodName, returnParam)
pf("%s(context.Context, "+paramStr+") (%s, api.HTTPError)", e.MethodName, returnParam)
} else {
p("%s(context.Context, "+paramStr+") (api.HTTPError)", e.MethodName)
pf("%s(context.Context, "+paramStr+") (api.HTTPError)", e.MethodName)
}
}
p("}")
p("")
pf("}")
pf("")
}
for _, group := range a.EndpointGroups {
i("go.uber.org/zap", "github.com/spacemonkeygo/monkit/v3")
p("// %sHandler is an api handler that exposes all %s related functionality.", group.Name, group.Prefix)
p("type %sHandler struct {", group.Name)
p("log *zap.Logger")
p("mon *monkit.Scope")
p("service %sService", group.Name)
p("auth api.Auth")
p("}")
p("")
pf("// %sHandler is an api handler that exposes all %s related functionality.", group.Name, group.Prefix)
pf("type %sHandler struct {", group.Name)
pf("log *zap.Logger")
pf("mon *monkit.Scope")
pf("service %sService", group.Name)
pf("auth api.Auth")
pf("}")
pf("")
}
for _, group := range a.EndpointGroups {
i("github.com/gorilla/mux")
p(
pf(
"func New%s(log *zap.Logger, mon *monkit.Scope, service %sService, router *mux.Router, auth api.Auth) *%sHandler {",
group.Name,
group.Name,
group.Name,
)
p("handler := &%sHandler{", group.Name)
p("log: log,")
p("mon: mon,")
p("service: service,")
p("auth: auth,")
p("}")
p("")
p("%sRouter := router.PathPrefix(\"/api/v0/%s\").Subrouter()", group.Prefix, group.Prefix)
pf("handler := &%sHandler{", group.Name)
pf("log: log,")
pf("mon: mon,")
pf("service: service,")
pf("auth: auth,")
pf("}")
pf("")
pf("%sRouter := router.PathPrefix(\"/api/v0/%s\").Subrouter()", group.Prefix, group.Prefix)
for _, endpoint := range group.endpoints {
handlerName := "handle" + endpoint.MethodName
p("%sRouter.HandleFunc(\"%s\", handler.%s).Methods(\"%s\")", group.Prefix, endpoint.Path, handlerName, endpoint.Method)
pf("%sRouter.HandleFunc(\"%s\", handler.%s).Methods(\"%s\")", group.Prefix, endpoint.Path, handlerName, endpoint.Method)
}
p("")
p("return handler")
p("}")
p("")
pf("")
pf("return handler")
pf("}")
pf("")
}
for _, group := range a.EndpointGroups {
for _, endpoint := range group.endpoints {
i("net/http")
p("")
pf("")
handlerName := "handle" + endpoint.MethodName
p("func (h *%sHandler) %s(w http.ResponseWriter, r *http.Request) {", group.Name, handlerName)
p("ctx := r.Context()")
p("var err error")
p("defer h.mon.Task()(&ctx)(&err)")
p("")
pf("func (h *%sHandler) %s(w http.ResponseWriter, r *http.Request) {", group.Name, handlerName)
pf("ctx := r.Context()")
pf("var err error")
pf("defer h.mon.Task()(&ctx)(&err)")
pf("")
p("w.Header().Set(\"Content-Type\", \"application/json\")")
p("")
pf("w.Header().Set(\"Content-Type\", \"application/json\")")
pf("")
if !endpoint.NoCookieAuth || !endpoint.NoAPIAuth {
p("ctx, err = h.auth.IsAuthenticated(ctx, r, %v, %v)", !endpoint.NoCookieAuth, !endpoint.NoAPIAuth)
p("if err != nil {")
pf("ctx, err = h.auth.IsAuthenticated(ctx, r, %v, %v)", !endpoint.NoCookieAuth, !endpoint.NoAPIAuth)
pf("if err != nil {")
if !endpoint.NoCookieAuth {
p("h.auth.RemoveAuthCookie(w)")
pf("h.auth.RemoveAuthCookie(w)")
}
p("api.ServeError(h.log, w, http.StatusUnauthorized, err)")
p("return")
p("}")
p("")
pf("api.ServeError(h.log, w, http.StatusUnauthorized, err)")
pf("return")
pf("}")
pf("")
}
handleParams(p, i, endpoint.QueryParams, endpoint.PathParams)
handleParams(pf, i, endpoint.QueryParams, endpoint.PathParams)
if endpoint.Request != nil {
handleBody(p, endpoint.Request)
handleBody(pf, endpoint.Request)
}
var methodFormat string
@ -214,54 +214,54 @@ func (a *API) generateGo() ([]byte, error) {
}
methodFormat += ")"
p(methodFormat, endpoint.MethodName)
p("if httpErr.Err != nil {")
p("api.ServeError(h.log, w, httpErr.Status, httpErr.Err)")
pf(methodFormat, endpoint.MethodName)
pf("if httpErr.Err != nil {")
pf("api.ServeError(h.log, w, httpErr.Status, httpErr.Err)")
if endpoint.Response == nil {
p("}")
p("}")
pf("}")
pf("}")
continue
}
p("return")
p("}")
pf("return")
pf("}")
i("encoding/json")
p("")
p("err = json.NewEncoder(w).Encode(retVal)")
p("if err != nil {")
p("h.log.Debug(\"failed to write json %s response\", zap.Error(Err%sAPI.Wrap(err)))", endpoint.MethodName, cases.Title(language.Und).String(group.Prefix))
p("}")
p("}")
pf("")
pf("err = json.NewEncoder(w).Encode(retVal)")
pf("if err != nil {")
pf("h.log.Debug(\"failed to write json %s response\", zap.Error(Err%sAPI.Wrap(err)))", endpoint.MethodName, cases.Title(language.Und).String(group.Prefix))
pf("}")
pf("}")
}
}
fileBody := result
result = ""
p("// AUTOGENERATED BY private/apigen")
p("// DO NOT EDIT.")
p("")
pf("// AUTOGENERATED BY private/apigen")
pf("// DO NOT EDIT.")
pf("")
p("package %s", a.PackageName)
p("")
pf("package %s", a.PackageName)
pf("")
p("import (")
pf("import (")
slices := [][]string{imports.Standard, imports.External, imports.Internal}
for sn, slice := range slices {
sort.Strings(slice)
for pn, path := range slice {
p(`"%s"`, path)
pf(`"%s"`, path)
if pn == len(slice)-1 && sn < len(slices)-1 {
p("")
pf("")
}
}
}
p(")")
p("")
pf(")")
pf("")
if _, ok := imports.All["time"]; ok {
p("const dateLayout = \"%s\"", DateFormat)
p("")
pf("const dateLayout = \"%s\"", DateFormat)
pf("")
}
result += fileBody
@ -286,7 +286,7 @@ func (a *API) handleTypesPackage(t reflect.Type) string {
}
// handleParams handles parsing of URL query parameters or path parameters.
func handleParams(p func(format string, a ...interface{}), i func(paths ...string), queryParams, pathParams []Param) {
func handleParams(pf func(format string, a ...interface{}), i func(paths ...string), queryParams, pathParams []Param) {
for _, params := range []*[]Param{&queryParams, &pathParams} {
for _, param := range *params {
varName := param.Name
@ -296,50 +296,50 @@ func handleParams(p func(format string, a ...interface{}), i func(paths ...strin
switch params {
case &queryParams:
p("%s := r.URL.Query().Get(\"%s\")", varName, param.Name)
p("if %s == \"\" {", varName)
p("api.ServeError(h.log, w, http.StatusBadRequest, errs.New(\"parameter '%s' can't be empty\"))", param.Name)
p("return")
p("}")
p("")
pf("%s := r.URL.Query().Get(\"%s\")", varName, param.Name)
pf("if %s == \"\" {", varName)
pf("api.ServeError(h.log, w, http.StatusBadRequest, errs.New(\"parameter '%s' can't be empty\"))", param.Name)
pf("return")
pf("}")
pf("")
case &pathParams:
p("%s, ok := mux.Vars(r)[\"%s\"]", varName, param.Name)
p("if !ok {")
p("api.ServeError(h.log, w, http.StatusBadRequest, errs.New(\"missing %s route param\"))", param.Name)
p("return")
p("}")
p("")
pf("%s, ok := mux.Vars(r)[\"%s\"]", varName, param.Name)
pf("if !ok {")
pf("api.ServeError(h.log, w, http.StatusBadRequest, errs.New(\"missing %s route param\"))", param.Name)
pf("return")
pf("}")
pf("")
}
switch param.Type {
case reflect.TypeOf(uuid.UUID{}):
i("storj.io/common/uuid")
p("%s, err := uuid.FromString(%s)", param.Name, varName)
pf("%s, err := uuid.FromString(%s)", param.Name, varName)
case reflect.TypeOf(time.Time{}):
i("time")
p("%s, err := time.Parse(dateLayout, %s)", param.Name, varName)
pf("%s, err := time.Parse(dateLayout, %s)", param.Name, varName)
default:
p("")
pf("")
continue
}
p("if err != nil {")
p("api.ServeError(h.log, w, http.StatusBadRequest, err)")
p("return")
p("}")
p("")
pf("if err != nil {")
pf("api.ServeError(h.log, w, http.StatusBadRequest, err)")
pf("return")
pf("}")
pf("")
}
}
}
// handleBody handles request body.
func handleBody(p func(format string, a ...interface{}), body interface{}) {
p("payload := %s{}", reflect.TypeOf(body).String())
p("if err = json.NewDecoder(r.Body).Decode(&payload); err != nil {")
p("api.ServeError(h.log, w, http.StatusBadRequest, err)")
p("return")
p("}")
p("")
func handleBody(pf func(format string, a ...interface{}), body interface{}) {
pf("payload := %s{}", reflect.TypeOf(body).String())
pf("if err = json.NewDecoder(r.Body).Decode(&payload); err != nil {")
pf("api.ServeError(h.log, w, http.StatusBadRequest, err)")
pf("return")
pf("}")
pf("")
}
// getElementaryType simplifies a Go type.

288
private/apigen/tsgen.go Normal file
View File

@ -0,0 +1,288 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
package apigen
import (
"fmt"
"os"
"reflect"
"sort"
"strings"
"github.com/zeebo/errs"
)
// TODO maybe look at the Go structs MarshalJSON return type instead.
var tsTypeOverrides = map[string]string{
"uuid.UUID": "string",
"time.Time": "string",
"memory.Size": "string",
"[]uint8": "string", // e.g. []byte
}
// getBasicReflectType dereferences a pointer and gets the basic types from slices.
func getBasicReflectType(t reflect.Type) reflect.Type {
if t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice {
t = t.Elem()
}
return t
}
// tsType gets the corresponding typescript type for a provided reflect.Type.
// Input is expected to be a (non pointer) struct or primitive.
func tsType(t reflect.Type) string {
override := tsTypeOverrides[t.String()]
if len(override) > 0 {
return override
}
switch t.Kind() {
case reflect.Ptr:
return tsType(t.Elem())
case reflect.Slice, reflect.Array:
return tsType(t.Elem()) + "[]"
case reflect.String:
return "string"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return "number"
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return "number"
case reflect.Float32, reflect.Float64:
return "number"
case reflect.Bool:
return "boolean"
case reflect.Struct:
return t.Name()
default:
panic("unhandled type: " + t.Name())
}
}
// MustWriteTS writes generated TypeScript code into a file.
func (a *API) MustWriteTS(path string) {
f := newTSGenFile(path, a)
f.generateTS()
err := f.write()
if err != nil {
panic(errs.Wrap(err))
}
}
type tsGenFile struct {
result string
path string
// types is a map of struct types and their struct type dependencies.
// We use this to ensure all dependencies are written before their parent.
types map[reflect.Type][]reflect.Type
// typeList is a list of all struct types. We use this for sorting the types alphabetically
// to ensure that the diff is minimized when the file is regenerated.
typeList []reflect.Type
typesWritten map[reflect.Type]bool
api *API
}
func newTSGenFile(filepath string, api *API) *tsGenFile {
f := &tsGenFile{
path: filepath,
types: make(map[reflect.Type][]reflect.Type),
typesWritten: make(map[reflect.Type]bool),
api: api,
}
f.pf("// AUTOGENERATED BY private/apigen")
f.pf("// DO NOT EDIT.")
f.pf("")
f.pf("import { HttpClient } from '@/utils/httpClient'")
f.pf("")
return f
}
func (f *tsGenFile) pf(format string, a ...interface{}) {
f.result += fmt.Sprintf(format+"\n", a...)
}
func (f *tsGenFile) write() error {
return os.WriteFile(f.path, []byte(f.result), 0644)
}
func (f *tsGenFile) getStructsFromType(t reflect.Type) {
t = getBasicReflectType(t)
override := tsTypeOverrides[t.String()]
if len(override) > 0 {
return
}
// if it is a struct, get any types needed from the fields
if t.Kind() == reflect.Struct {
if _, ok := f.types[t]; !ok {
f.types[t] = []reflect.Type{}
f.typeList = append(f.typeList, t)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
f.getStructsFromType(field.Type)
if field.Type.Kind() == reflect.Struct {
deps := f.types[t]
deps = append(deps, getBasicReflectType(field.Type))
f.types[t] = deps
}
}
}
}
}
func (f *tsGenFile) generateTS() {
f.createClasses()
for _, group := range f.api.EndpointGroups {
// Not sure if this is a good name
f.createAPIClient(group)
}
}
func (f *tsGenFile) emitStruct(t reflect.Type) {
override := tsTypeOverrides[t.String()]
if len(override) > 0 {
return
}
if f.typesWritten[t] {
return
}
if t.Kind() != reflect.Struct {
// TODO: handle slices
// I'm not sure this is necessary. If it's not a struct then we don't need to create a TS class for it.
// We just use a JS array of the class.
return
}
for _, d := range f.types[t] {
if f.typesWritten[d] {
continue
}
f.emitStruct(d)
}
f.pf("class %s {", t.Name())
defer f.pf("}\n")
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
attributes := strings.Fields(field.Tag.Get("json"))
if len(attributes) == 0 || attributes[0] == "" {
panic(t.Name() + " missing json declaration")
}
if attributes[0] == "-" {
continue
}
f.pf("\t%s: %s;", attributes[0], tsType(field.Type))
}
f.typesWritten[t] = true
}
func (f *tsGenFile) createClasses() {
for _, group := range f.api.EndpointGroups {
for _, method := range group.endpoints {
if method.Request != nil {
reqType := reflect.TypeOf(method.Request)
f.getStructsFromType(reqType)
}
if method.Response != nil {
resType := reflect.TypeOf(method.Response)
f.getStructsFromType(resType)
}
if len(method.QueryParams) > 0 {
for _, p := range method.QueryParams {
t := getBasicReflectType(p.Type)
f.getStructsFromType(t)
}
}
}
}
sort.Slice(f.typeList, func(i, j int) bool {
return strings.Compare(f.typeList[i].Name(), f.typeList[j].Name()) < 0
})
for _, t := range f.typeList {
f.emitStruct(t)
}
}
func (f *tsGenFile) createAPIClient(group *EndpointGroup) {
f.pf("export class %sHttpApi%s {", group.Prefix, strings.ToUpper(f.api.Version))
f.pf("\tprivate readonly http: HttpClient = new HttpClient();")
f.pf("\tprivate readonly ROOT_PATH: string = '/api/%s/%s';", f.api.Version, group.Prefix)
f.pf("")
for _, method := range group.endpoints {
funcArgs, path := f.getArgsAndPath(method)
returnStmt := "return"
returnType := "void"
if method.Response != nil {
returnType = tsType(getBasicReflectType(reflect.TypeOf(method.Response)))
if v := reflect.ValueOf(method.Response); v.Kind() == reflect.Array || v.Kind() == reflect.Slice {
returnType = fmt.Sprintf("Array<%s>", returnType)
}
returnStmt += fmt.Sprintf(" response.json().then((body) => body as %s)", returnType)
}
returnStmt += ";"
f.pf("\tpublic async %s(%s): Promise<%s> {", method.RequestName, funcArgs, returnType)
f.pf("\t\tconst path = `%s`;", path)
if method.Request != nil {
f.pf("\t\tconst response = await this.http.%s(path, JSON.stringify(request));", strings.ToLower(method.Method))
} else {
f.pf("\t\tconst response = await this.http.%s(path);", strings.ToLower(method.Method))
}
f.pf("\t\tif (response.ok) {")
f.pf("\t\t\t%s", returnStmt)
f.pf("\t\t}")
f.pf("\t\tconst err = await response.json()")
f.pf("\t\tthrow new Error(err.error)")
f.pf("\t}\n")
}
f.pf("}")
}
func (f *tsGenFile) getArgsAndPath(method *fullEndpoint) (funcArgs, path string) {
// remove path parameter placeholders
path = method.Path
i := strings.Index(path, "{")
if i > -1 {
path = method.Path[:i]
}
path = "${this.ROOT_PATH}" + path
if method.Request != nil {
t := getBasicReflectType(reflect.TypeOf(method.Request))
funcArgs += fmt.Sprintf("request: %s, ", tsType(t))
}
for _, p := range method.PathParams {
funcArgs += fmt.Sprintf("%s: %s, ", p.Name, tsType(p.Type))
path += fmt.Sprintf("/${%s}", p.Name)
}
for i, p := range method.QueryParams {
if i == 0 {
path += "?"
} else {
path += "&"
}
funcArgs += fmt.Sprintf("%s: %s, ", p.Name, tsType(p.Type))
path += fmt.Sprintf("%s=${%s}", p.Name, p.Name)
}
path = strings.ReplaceAll(path, "//", "/")
return strings.Trim(funcArgs, ", "), path
}

View File

@ -6,6 +6,7 @@ package main
//go:generate go run ./
import (
"fmt"
"time"
"storj.io/common/uuid"
@ -29,6 +30,7 @@ func main() {
Name: "Create new Project",
Description: "Creates new Project with given info",
MethodName: "GenCreateProject",
RequestName: "createProject",
Response: &console.Project{},
Request: console.ProjectInfo{},
})
@ -37,6 +39,7 @@ func main() {
Name: "Update Project",
Description: "Updates project with given info",
MethodName: "GenUpdateProject",
RequestName: "updateProject",
Response: console.Project{},
Request: console.ProjectInfo{},
PathParams: []apigen.Param{
@ -48,6 +51,7 @@ func main() {
Name: "Delete Project",
Description: "Deletes project by id",
MethodName: "GenDeleteProject",
RequestName: "deleteProject",
PathParams: []apigen.Param{
apigen.NewParam("id", uuid.UUID{}),
},
@ -57,6 +61,7 @@ func main() {
Name: "Get Projects",
Description: "Gets all projects user has",
MethodName: "GenGetUsersProjects",
RequestName: "getProjects",
Response: []console.Project{},
})
@ -64,6 +69,7 @@ func main() {
Name: "Get Project's Single Bucket Usage",
Description: "Gets project's single bucket usage by bucket ID",
MethodName: "GenGetSingleBucketUsageRollup",
RequestName: "getBucketRollup",
Response: accounting.BucketUsageRollup{},
QueryParams: []apigen.Param{
apigen.NewParam("projectID", uuid.UUID{}),
@ -77,6 +83,7 @@ func main() {
Name: "Get Project's All Buckets Usage",
Description: "Gets project's all buckets usage",
MethodName: "GenGetBucketUsageRollups",
RequestName: "getBucketRollups",
Response: []accounting.BucketUsageRollup{},
QueryParams: []apigen.Param{
apigen.NewParam("projectID", uuid.UUID{}),
@ -93,6 +100,7 @@ func main() {
Name: "Create new macaroon API key",
Description: "Creates new macaroon API key with given info",
MethodName: "GenCreateAPIKey",
RequestName: "createAPIKey",
Response: console.CreateAPIKeyResponse{},
Request: console.CreateAPIKeyRequest{},
})
@ -105,9 +113,11 @@ func main() {
Name: "Get User",
Description: "Gets User by request context",
MethodName: "GenGetUser",
RequestName: "getUser",
Response: console.ResponseUser{},
})
}
a.MustWriteGo("satellite/console/consoleweb/consoleapi/api.gen.go")
a.MustWriteTS(fmt.Sprintf("web/satellite/src/api/%s.gen.ts", a.Version))
}

View File

@ -0,0 +1,177 @@
// AUTOGENERATED BY private/apigen
// DO NOT EDIT.
import { HttpClient } from '@/utils/httpClient'
class APIKeyInfo {
id: string;
projectId: string;
partnerId: string;
userAgent: string;
name: string;
createdAt: string;
}
class BucketUsageRollup {
projectID: string;
bucketName: string;
totalStoredData: number;
totalSegments: number;
objectCount: number;
metadataSize: number;
repairEgress: number;
getEgress: number;
auditEgress: number;
since: string;
before: string;
}
class CreateAPIKeyRequest {
projectID: string;
name: string;
}
class CreateAPIKeyResponse {
key: string;
keyInfo: APIKeyInfo;
}
class Project {
id: string;
publicId: string;
name: string;
description: string;
partnerId: string;
userAgent: string;
ownerId: string;
rateLimit: number;
burstLimit: number;
maxBuckets: number;
createdAt: string;
memberCount: number;
storageLimit: string;
bandwidthLimit: string;
segmentLimit: number;
}
class ProjectInfo {
name: string;
description: string;
storageLimit: string;
bandwidthLimit: string;
createdAt: string;
}
class ResponseUser {
id: string;
fullName: string;
shortName: string;
email: string;
partnerId: string;
userAgent: string;
projectLimit: number;
isProfessional: boolean;
position: string;
companyName: string;
employeeCount: string;
haveSalesContact: boolean;
paidTier: boolean;
isMFAEnabled: boolean;
mfaRecoveryCodeCount: number;
}
export class projectsHttpApiV0 {
private readonly http: HttpClient = new HttpClient();
private readonly ROOT_PATH: string = '/api/v0/projects';
public async createProject(request: ProjectInfo): Promise<Project> {
const path = `${this.ROOT_PATH}/create`;
const response = await this.http.post(path, JSON.stringify(request));
if (response.ok) {
return response.json().then((body) => body as Project);
}
const err = await response.json()
throw new Error(err.error)
}
public async updateProject(request: ProjectInfo, id: string): Promise<Project> {
const path = `${this.ROOT_PATH}/update/${id}`;
const response = await this.http.patch(path, JSON.stringify(request));
if (response.ok) {
return response.json().then((body) => body as Project);
}
const err = await response.json()
throw new Error(err.error)
}
public async deleteProject(id: string): Promise<void> {
const path = `${this.ROOT_PATH}/delete/${id}`;
const response = await this.http.delete(path);
if (response.ok) {
return;
}
const err = await response.json()
throw new Error(err.error)
}
public async getProjects(): Promise<Array<Project>> {
const path = `${this.ROOT_PATH}/`;
const response = await this.http.get(path);
if (response.ok) {
return response.json().then((body) => body as Array<Project>);
}
const err = await response.json()
throw new Error(err.error)
}
public async getBucketRollup(projectID: string, bucket: string, since: string, before: string): Promise<BucketUsageRollup> {
const path = `${this.ROOT_PATH}/bucket-rollup?projectID=${projectID}&bucket=${bucket}&since=${since}&before=${before}`;
const response = await this.http.get(path);
if (response.ok) {
return response.json().then((body) => body as BucketUsageRollup);
}
const err = await response.json()
throw new Error(err.error)
}
public async getBucketRollups(projectID: string, since: string, before: string): Promise<Array<BucketUsageRollup>> {
const path = `${this.ROOT_PATH}/bucket-rollups?projectID=${projectID}&since=${since}&before=${before}`;
const response = await this.http.get(path);
if (response.ok) {
return response.json().then((body) => body as Array<BucketUsageRollup>);
}
const err = await response.json()
throw new Error(err.error)
}
}
export class apikeysHttpApiV0 {
private readonly http: HttpClient = new HttpClient();
private readonly ROOT_PATH: string = '/api/v0/apikeys';
public async createAPIKey(request: CreateAPIKeyRequest): Promise<CreateAPIKeyResponse> {
const path = `${this.ROOT_PATH}/create`;
const response = await this.http.post(path, JSON.stringify(request));
if (response.ok) {
return response.json().then((body) => body as CreateAPIKeyResponse);
}
const err = await response.json()
throw new Error(err.error)
}
}
export class usersHttpApiV0 {
private readonly http: HttpClient = new HttpClient();
private readonly ROOT_PATH: string = '/api/v0/users';
public async getUser(): Promise<ResponseUser> {
const path = `${this.ROOT_PATH}/`;
const response = await this.http.get(path);
if (response.ok) {
return response.json().then((body) => body as ResponseUser);
}
const err = await response.json()
throw new Error(err.error)
}
}