Merge pull request 'Finish export endpoint' (#51) from exportfinish into trunk

Reviewed-on: #51
This commit is contained in:
Daniel 2024-11-24 21:53:56 -05:00
commit e7308edd4c
5 changed files with 132 additions and 7 deletions

View 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
}

View file

@ -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 {

View file

@ -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())

View file

@ -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

View file

@ -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)