Support simple jsonl exports for records
This commit is contained in:
parent
c1e3231d00
commit
0707f139c4
@ -655,6 +655,48 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "export",
|
||||
"path": "/export{filename}.{ext}",
|
||||
"method": "GET",
|
||||
"title": "Exports records that match ",
|
||||
"parameters": {
|
||||
"path": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "filename",
|
||||
"required": false,
|
||||
"title": "Filename to use"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "ext",
|
||||
"required": true,
|
||||
"title": "Export format"
|
||||
}
|
||||
],
|
||||
"get": [
|
||||
{
|
||||
"name": "filter",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"title": "Filtering condition"
|
||||
},
|
||||
{
|
||||
"name": "sort",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"title": "Sort field (default id desc)"
|
||||
},
|
||||
{
|
||||
"name": "download",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"title": "Send headers to browser to trigger download/save-as"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "create",
|
||||
"method": "POST",
|
||||
|
||||
@ -91,6 +91,48 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "export",
|
||||
"Method": "GET",
|
||||
"Title": "Exports records that match ",
|
||||
"Path": "/export{filename}.{ext}",
|
||||
"Parameters": {
|
||||
"get": [
|
||||
{
|
||||
"name": "filter",
|
||||
"required": false,
|
||||
"title": "Filtering condition",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "sort",
|
||||
"required": false,
|
||||
"title": "Sort field (default id desc)",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "download",
|
||||
"required": false,
|
||||
"title": "Send headers to browser to trigger download/save-as",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"path": [
|
||||
{
|
||||
"name": "filename",
|
||||
"required": false,
|
||||
"title": "Filename to use",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "ext",
|
||||
"required": true,
|
||||
"title": "Export format",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "create",
|
||||
"Method": "POST",
|
||||
|
||||
@ -31,6 +31,7 @@ import (
|
||||
type RecordAPI interface {
|
||||
Report(context.Context, *request.RecordReport) (interface{}, error)
|
||||
List(context.Context, *request.RecordList) (interface{}, error)
|
||||
Export(context.Context, *request.RecordExport) (interface{}, error)
|
||||
Create(context.Context, *request.RecordCreate) (interface{}, error)
|
||||
Read(context.Context, *request.RecordRead) (interface{}, error)
|
||||
Update(context.Context, *request.RecordUpdate) (interface{}, error)
|
||||
@ -42,6 +43,7 @@ type RecordAPI interface {
|
||||
type Record struct {
|
||||
Report func(http.ResponseWriter, *http.Request)
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Export func(http.ResponseWriter, *http.Request)
|
||||
Create func(http.ResponseWriter, *http.Request)
|
||||
Read func(http.ResponseWriter, *http.Request)
|
||||
Update func(http.ResponseWriter, *http.Request)
|
||||
@ -91,6 +93,26 @@ func NewRecord(h RecordAPI) *Record {
|
||||
resputil.JSON(w, value)
|
||||
}
|
||||
},
|
||||
Export: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordExport()
|
||||
if err := params.Fill(r); err != nil {
|
||||
logger.LogParamError("Record.Export", r, err)
|
||||
resputil.JSON(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Export(r.Context(), params)
|
||||
if err != nil {
|
||||
logger.LogControllerError("Record.Export", r, err, params.Auditable())
|
||||
resputil.JSON(w, err)
|
||||
return
|
||||
}
|
||||
logger.LogControllerCall("Record.Export", r, params.Auditable())
|
||||
if !serveHTTP(value, w, r) {
|
||||
resputil.JSON(w, value)
|
||||
}
|
||||
},
|
||||
Create: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewRecordCreate()
|
||||
@ -199,6 +221,7 @@ func (h Record) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http
|
||||
r.Use(middlewares...)
|
||||
r.Get("/namespace/{namespaceID}/module/{moduleID}/record/report", h.Report)
|
||||
r.Get("/namespace/{namespaceID}/module/{moduleID}/record/", h.List)
|
||||
r.Get("/namespace/{namespaceID}/module/{moduleID}/record/export{filename}.{ext}", h.Export)
|
||||
r.Post("/namespace/{namespaceID}/module/{moduleID}/record/", h.Create)
|
||||
r.Get("/namespace/{namespaceID}/module/{moduleID}/record/{recordID}", h.Read)
|
||||
r.Post("/namespace/{namespaceID}/module/{moduleID}/record/{recordID}", h.Update)
|
||||
|
||||
@ -2,6 +2,9 @@ package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/titpetric/factory/resputil"
|
||||
|
||||
@ -154,6 +157,45 @@ func (ctrl *Record) Upload(ctx context.Context, r *request.RecordUpload) (interf
|
||||
return makeAttachmentPayload(ctx, a, err)
|
||||
}
|
||||
|
||||
func (ctrl *Record) Export(ctx context.Context, r *request.RecordExport) (interface{}, error) {
|
||||
var (
|
||||
m *types.Module
|
||||
err error
|
||||
)
|
||||
|
||||
if m, err = ctrl.module.With(ctx).FindByID(r.NamespaceID, r.ModuleID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = m
|
||||
|
||||
// will probably have to rewrite exporting into something more optimal:
|
||||
// maybe pass encoding function/callback directly
|
||||
rr, _, err := ctrl.record.With(ctx).Find(types.RecordFilter{
|
||||
NamespaceID: r.NamespaceID,
|
||||
ModuleID: r.ModuleID,
|
||||
Filter: r.Filter,
|
||||
Sort: r.Sort,
|
||||
})
|
||||
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
var (
|
||||
enc = json.NewEncoder(w)
|
||||
filename = fmt.Sprintf("; filename=%s.%s", r.Filename, r.Ext)
|
||||
)
|
||||
|
||||
if r.Download {
|
||||
w.Header().Add("Content-Disposition", "attachment"+filename)
|
||||
} else {
|
||||
w.Header().Add("Content-Disposition", "inline"+filename)
|
||||
}
|
||||
|
||||
_ = rr.Walk(func(record *types.Record) error {
|
||||
return enc.Encode(record)
|
||||
})
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ctrl Record) makePayload(ctx context.Context, m *types.Module, r *types.Record, err error) (*recordPayload, error) {
|
||||
if err != nil || r == nil {
|
||||
return nil, err
|
||||
|
||||
@ -175,6 +175,81 @@ func (r *RecordList) Fill(req *http.Request) (err error) {
|
||||
|
||||
var _ RequestFiller = NewRecordList()
|
||||
|
||||
// Record export request parameters
|
||||
type RecordExport struct {
|
||||
Filter string
|
||||
Sort string
|
||||
Download bool
|
||||
Filename string
|
||||
Ext string
|
||||
NamespaceID uint64 `json:",string"`
|
||||
ModuleID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
func NewRecordExport() *RecordExport {
|
||||
return &RecordExport{}
|
||||
}
|
||||
|
||||
func (r RecordExport) Auditable() map[string]interface{} {
|
||||
var out = map[string]interface{}{}
|
||||
|
||||
out["filter"] = r.Filter
|
||||
out["sort"] = r.Sort
|
||||
out["download"] = r.Download
|
||||
out["filename"] = r.Filename
|
||||
out["ext"] = r.Ext
|
||||
out["namespaceID"] = r.NamespaceID
|
||||
out["moduleID"] = r.ModuleID
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (r *RecordExport) Fill(req *http.Request) (err error) {
|
||||
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(req.Body).Decode(r)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return errors.Wrap(err, "error parsing http request body")
|
||||
}
|
||||
}
|
||||
|
||||
if err = req.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := map[string]string{}
|
||||
post := map[string]string{}
|
||||
urlQuery := req.URL.Query()
|
||||
for name, param := range urlQuery {
|
||||
get[name] = string(param[0])
|
||||
}
|
||||
postVars := req.Form
|
||||
for name, param := range postVars {
|
||||
post[name] = string(param[0])
|
||||
}
|
||||
|
||||
if val, ok := get["filter"]; ok {
|
||||
r.Filter = val
|
||||
}
|
||||
if val, ok := get["sort"]; ok {
|
||||
r.Sort = val
|
||||
}
|
||||
if val, ok := get["download"]; ok {
|
||||
r.Download = parseBool(val)
|
||||
}
|
||||
r.Filename = chi.URLParam(req, "filename")
|
||||
r.Ext = chi.URLParam(req, "ext")
|
||||
r.NamespaceID = parseUInt64(chi.URLParam(req, "namespaceID"))
|
||||
r.ModuleID = parseUInt64(chi.URLParam(req, "moduleID"))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewRecordExport()
|
||||
|
||||
// Record create request parameters
|
||||
type RecordCreate struct {
|
||||
Values types.RecordValueSet
|
||||
|
||||
@ -681,6 +681,7 @@ Compose records
|
||||
| ------ | -------- | ------- |
|
||||
| `GET` | `/namespace/{namespaceID}/module/{moduleID}/record/report` | Generates report from module records |
|
||||
| `GET` | `/namespace/{namespaceID}/module/{moduleID}/record/` | List/read records from module section |
|
||||
| `GET` | `/namespace/{namespaceID}/module/{moduleID}/record/export{filename}.{ext}` | Exports records that match |
|
||||
| `POST` | `/namespace/{namespaceID}/module/{moduleID}/record/` | Create record in module section |
|
||||
| `GET` | `/namespace/{namespaceID}/module/{moduleID}/record/{recordID}` | Read records by ID from module section |
|
||||
| `POST` | `/namespace/{namespaceID}/module/{moduleID}/record/{recordID}` | Update records in module section |
|
||||
@ -724,6 +725,26 @@ Compose records
|
||||
| namespaceID | uint64 | PATH | Namespace ID | N/A | YES |
|
||||
| moduleID | uint64 | PATH | Module ID | N/A | YES |
|
||||
|
||||
## Exports records that match
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/namespace/{namespaceID}/module/{moduleID}/record/export{filename}.{ext}` | HTTP/S | GET | |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Method | Description | Default | Required? |
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| filter | string | GET | Filtering condition | N/A | NO |
|
||||
| sort | string | GET | Sort field (default id desc) | N/A | NO |
|
||||
| download | bool | GET | Send headers to browser to trigger download/save-as | N/A | NO |
|
||||
| filename | string | PATH | Filename to use | N/A | NO |
|
||||
| ext | string | PATH | Export format | N/A | YES |
|
||||
| namespaceID | uint64 | PATH | Namespace ID | N/A | YES |
|
||||
| moduleID | uint64 | PATH | Module ID | N/A | YES |
|
||||
|
||||
## Create record in module section
|
||||
|
||||
#### Method
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user