Merge pull request 'Finish export endpoint' (#51) from exportfinish into trunk
Reviewed-on: #51
This commit is contained in:
commit
e7308edd4c
5 changed files with 132 additions and 7 deletions
53
internal/forms/multipart_test.go
Normal file
53
internal/forms/multipart_test.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package forms_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/xport"
|
||||
)
|
||||
|
||||
func perr(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func makeExportRequest(ej *xport.ExportJob, url string) *http.Request {
|
||||
var buf bytes.Buffer
|
||||
body := multipart.NewWriter(&buf)
|
||||
|
||||
perr(body.WriteField("type", string(ej.Type)))
|
||||
|
||||
perr(body.WriteField("systemID", strconv.Itoa(int(ej.SystemID))))
|
||||
|
||||
w, err := body.CreateFormFile("template", ej.TemplateFileName)
|
||||
perr(err)
|
||||
|
||||
_, err = w.Write(ej.Template)
|
||||
perr(err)
|
||||
|
||||
r, err := http.NewRequest(http.MethodPost, url, &buf)
|
||||
perr(err)
|
||||
|
||||
r.Header.Set("Content-Type", body.FormDataContentType())
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func makeFunkyJSONExportRequest(ej *xport.ExportJob, url string) *http.Request {
|
||||
var buf bytes.Buffer
|
||||
je := json.NewEncoder(&buf)
|
||||
|
||||
err := je.Encode(ej)
|
||||
perr(err)
|
||||
|
||||
r, err := http.NewRequest(http.MethodPost, url, &buf)
|
||||
perr(err)
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
|
||||
return r
|
||||
}
|
|
@ -15,6 +15,7 @@ import (
|
|||
"dynatron.me/x/stillbox/pkg/alerting"
|
||||
"dynatron.me/x/stillbox/pkg/config"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/xport"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -124,6 +125,13 @@ var (
|
|||
},
|
||||
OrderBy: common.PtrTo(tgstore.TGOrderID),
|
||||
}
|
||||
|
||||
ExpJob1 = xport.ExportJob{
|
||||
Type: xport.FormatSDRTrunk,
|
||||
SystemID: 197,
|
||||
Template: []byte("this is a template\n\r\nthingy"),
|
||||
TemplateFileName: "template.xml",
|
||||
}
|
||||
)
|
||||
|
||||
func makeRequest(fixture string) *http.Request {
|
||||
|
@ -238,6 +246,13 @@ func TestUnmarshal(t *testing.T) {
|
|||
expect: &Pag1,
|
||||
opts: []forms.Option{forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty()},
|
||||
},
|
||||
{
|
||||
name: "non multipart byte field",
|
||||
r: makeFunkyJSONExportRequest(&ExpJob1, "http://somewhere/export"),
|
||||
dest: &xport.ExportJob{},
|
||||
expect: &ExpJob1,
|
||||
opts: []forms.Option{forms.WithAcceptBlank(), forms.WithOmitEmpty()},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/forms"
|
||||
|
@ -32,6 +33,8 @@ func (tga *talkgroupAPI) Subrouter() http.Handler {
|
|||
|
||||
r.Post("/import", tga.tgImport)
|
||||
|
||||
r.Post("/export", tga.tgExport)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
|
@ -156,6 +159,25 @@ func (tga *talkgroupAPI) put(w http.ResponseWriter, r *http.Request) {
|
|||
respond(w, r, record)
|
||||
}
|
||||
|
||||
func (tga *talkgroupAPI) tgExport(w http.ResponseWriter, r *http.Request) {
|
||||
var expJob xport.ExportJob
|
||||
ctx := r.Context()
|
||||
|
||||
err := forms.Unmarshal(r, &expJob, forms.WithAcceptBlank(), forms.WithOmitEmpty())
|
||||
if err != nil {
|
||||
wErr(w, r, badRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=stillbox_%s", expJob.TemplateFileName))
|
||||
w.Header().Set("Content-Type", "text/xml")
|
||||
|
||||
err = expJob.Export(ctx, w)
|
||||
if err != nil {
|
||||
wErr(w, r, autoError(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (tga *talkgroupAPI) tgImport(w http.ResponseWriter, r *http.Request) {
|
||||
var impJob xport.ImportJob
|
||||
err := forms.Unmarshal(r, &impJob, forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty())
|
||||
|
|
|
@ -15,9 +15,10 @@ type Exporter interface {
|
|||
}
|
||||
|
||||
type ExportJob struct {
|
||||
Type Format `json:"type"`
|
||||
SystemID int `json:"systemID"`
|
||||
Template []byte `json:"template"`
|
||||
Type Format `json:"type" form:"type"`
|
||||
SystemID int `json:"systemID" form:"systemID"`
|
||||
Template []byte `json:"template" form:"template" filenameField:"TemplateFileName"`
|
||||
TemplateFileName string
|
||||
|
||||
filter.TalkgroupFilter
|
||||
Exporter
|
||||
|
|
|
@ -15,6 +15,24 @@ type Playlist struct {
|
|||
Aliases []Alias `xml:"alias"`
|
||||
Channels []Channel `xml:"channel,omitempty"`
|
||||
Streams []Stream `xml:"stream,omitempty"`
|
||||
|
||||
streams map[string]struct{}
|
||||
}
|
||||
|
||||
func (p *Playlist) buildMaps() {
|
||||
p.streams = make(map[string]struct{})
|
||||
|
||||
for _, s := range p.Streams {
|
||||
if s.Type == "RDIOSCANNER_CALL" {
|
||||
p.streams[s.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Playlist) HasStream(name string) bool {
|
||||
_, has := p.streams[name]
|
||||
|
||||
return has
|
||||
}
|
||||
|
||||
type Alias struct {
|
||||
|
@ -27,20 +45,32 @@ type Alias struct {
|
|||
IDs []ID `xml:"id"`
|
||||
}
|
||||
|
||||
func tgToAlias(tg *talkgroups.Talkgroup) Alias {
|
||||
return Alias{
|
||||
func (p *Playlist) tgToAlias(tg *talkgroups.Talkgroup) Alias {
|
||||
a := Alias{
|
||||
XMLName: xml.Name{Local: "alias"},
|
||||
Name: common.ZeroIfNil(tg.Name),
|
||||
Group: common.ZeroIfNil(tg.TGGroup),
|
||||
List: "Stillbox",
|
||||
IDs: []ID{
|
||||
ID{
|
||||
{
|
||||
XMLName: xml.Name{Local: "id"},
|
||||
Type: "talkgroup",
|
||||
Value: common.PtrTo(int(tg.TGID)),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// be nice and assign it to stream to ourselves
|
||||
// TODO: make this more dynamic (exporter can have options, enumerate fields into a map[string]blah?)
|
||||
// with which to specify the stillbox streamer
|
||||
if p.HasStream("stillbox") {
|
||||
a.IDs = append(a.IDs, ID{
|
||||
Type: "broadcastChannel",
|
||||
Channel: common.PtrTo("stillbox"),
|
||||
})
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
type ID struct {
|
||||
|
@ -91,6 +121,8 @@ type RecordConfig struct {
|
|||
}
|
||||
|
||||
type Stream struct {
|
||||
Type string `xml:"type,attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
Attributes []xml.Attr `xml:",any,attr"`
|
||||
Stream []byte `xml:",innerxml"`
|
||||
}
|
||||
|
@ -113,8 +145,10 @@ func (st *Driver) ExportTalkgroups(ctx context.Context, w io.Writer, tgs []*talk
|
|||
pl.Aliases = nil
|
||||
}
|
||||
|
||||
pl.buildMaps()
|
||||
|
||||
for _, tg := range tgs {
|
||||
pl.Aliases = append(pl.Aliases, tgToAlias(tg))
|
||||
pl.Aliases = append(pl.Aliases, pl.tgToAlias(tg))
|
||||
}
|
||||
|
||||
enc := xml.NewEncoder(w)
|
||||
|
|
Loading…
Reference in a new issue