Properly save audio and image notes when uploaded
Also fix the audio processing pipeline.
This commit is contained in:
parent
4d02357671
commit
39d9f6d258
11 changed files with 145 additions and 138 deletions
63
api/api.go
63
api/api.go
|
|
@ -16,6 +16,8 @@ import (
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/queue"
|
"github.com/Gleipnir-Technology/nidus-sync/queue"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/userfile"
|
"github.com/Gleipnir-Technology/nidus-sync/userfile"
|
||||||
|
"github.com/aarondl/opt/omit"
|
||||||
|
"github.com/aarondl/opt/omitnull"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
@ -41,7 +43,18 @@ func apiAudioPost(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||||
http.Error(w, "Failed to decode the payload", http.StatusBadRequest)
|
http.Error(w, "Failed to decode the payload", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := db.NoteAudioCreate(context.Background(), noteUUID, db.NoteAudio{}, u.ID); err != nil {
|
setter := models.NoteAudioSetter{
|
||||||
|
Created: omit.From(payload.Created),
|
||||||
|
CreatorID: omit.From(u.ID),
|
||||||
|
Deleted: omitnull.FromPtr(payload.Deleted),
|
||||||
|
DeletorID: omitnull.FromPtr(payload.DeletorID),
|
||||||
|
Duration: omit.From(payload.Duration),
|
||||||
|
Transcription: omitnull.FromPtr(payload.Transcription),
|
||||||
|
TranscriptionUserEdited: omit.From(payload.TranscriptionUserEdited),
|
||||||
|
Version: omit.From(payload.Version),
|
||||||
|
UUID: omit.From(noteUUID),
|
||||||
|
}
|
||||||
|
if err := db.NoteAudioCreate(context.Background(), u.R.Organization, u.ID, setter); err != nil {
|
||||||
render.Render(w, r, errRender(err))
|
render.Render(w, r, errRender(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -92,8 +105,15 @@ func handleClientIos(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var since_used time.Time
|
||||||
|
if since == nil {
|
||||||
|
since_used = time.Unix(0, 0)
|
||||||
|
} else {
|
||||||
|
since_used = *since
|
||||||
|
}
|
||||||
response := ResponseClientIos{
|
response := ResponseClientIos{
|
||||||
Fieldseeker: toResponseFieldseeker(csync.Fieldseeker),
|
Fieldseeker: toResponseFieldseeker(csync.Fieldseeker),
|
||||||
|
Since: since_used,
|
||||||
}
|
}
|
||||||
if err := render.Render(w, r, response); err != nil {
|
if err := render.Render(w, r, response); err != nil {
|
||||||
render.Render(w, r, errRender(err))
|
render.Render(w, r, errRender(err))
|
||||||
|
|
@ -101,31 +121,6 @@ func handleClientIos(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func apiClientIosNotePut(w http.ResponseWriter, r *http.Request, u *models.User) {
|
|
||||||
id := chi.URLParam(r, "uuid")
|
|
||||||
noteUUID, err := uuid.Parse(id)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Failed to decode the uuid", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var payload NidusNotePayload
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Failed to read the payload", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(body, &payload); err != nil {
|
|
||||||
debugSaveRequest(body, err, "Note PUT JSON decode error")
|
|
||||||
http.Error(w, "Failed to decode the payload", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := db.NoteUpdate(context.Background(), noteUUID, db.NidusNotePayload{}); err != nil {
|
|
||||||
render.Render(w, r, errRender(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
}
|
|
||||||
|
|
||||||
func apiImagePost(w http.ResponseWriter, r *http.Request, u *models.User) {
|
func apiImagePost(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||||
id := chi.URLParam(r, "uuid")
|
id := chi.URLParam(r, "uuid")
|
||||||
noteUUID, err := uuid.Parse(id)
|
noteUUID, err := uuid.Parse(id)
|
||||||
|
|
@ -145,7 +140,15 @@ func apiImagePost(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||||
http.Error(w, "Failed to decode the payload", http.StatusBadRequest)
|
http.Error(w, "Failed to decode the payload", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = db.NoteImageCreate(context.Background(), noteUUID, db.NoteImage{}, u.ID)
|
setter := models.NoteImageSetter{
|
||||||
|
Created: omit.From(payload.Created),
|
||||||
|
CreatorID: omit.From(u.ID),
|
||||||
|
Deleted: omitnull.FromPtr(payload.Deleted),
|
||||||
|
DeletorID: omitnull.FromPtr(payload.DeletorID),
|
||||||
|
Version: omit.From(payload.Version),
|
||||||
|
UUID: omit.From(noteUUID),
|
||||||
|
}
|
||||||
|
err = db.NoteImageCreate(context.Background(), u.R.Organization, u.ID, setter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Render(w, r, errRender(err))
|
render.Render(w, r, errRender(err))
|
||||||
return
|
return
|
||||||
|
|
@ -187,7 +190,7 @@ func apiMosquitoSource(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||||
}
|
}
|
||||||
|
|
||||||
data := []render.Renderer{}
|
data := []render.Renderer{}
|
||||||
for _, s := range *sources {
|
for _, s := range sources {
|
||||||
data = append(data, NewResponseMosquitoSource(s))
|
data = append(data, NewResponseMosquitoSource(s))
|
||||||
}
|
}
|
||||||
if err := render.RenderList(w, r, data); err != nil {
|
if err := render.RenderList(w, r, data); err != nil {
|
||||||
|
|
@ -212,7 +215,7 @@ func apiTrapData(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||||
}
|
}
|
||||||
|
|
||||||
data := []render.Renderer{}
|
data := []render.Renderer{}
|
||||||
for _, td := range *trap_data {
|
for _, td := range trap_data {
|
||||||
data = append(data, NewResponseTrapDatum(td))
|
data = append(data, NewResponseTrapDatum(td))
|
||||||
}
|
}
|
||||||
if err := render.RenderList(w, r, data); err != nil {
|
if err := render.RenderList(w, r, data); err != nil {
|
||||||
|
|
@ -236,7 +239,7 @@ func apiServiceRequest(w http.ResponseWriter, r *http.Request, u *models.User) {
|
||||||
}
|
}
|
||||||
|
|
||||||
data := []render.Renderer{}
|
data := []render.Renderer{}
|
||||||
for _, sr := range *requests {
|
for _, sr := range requests {
|
||||||
data = append(data, NewResponseServiceRequest(sr))
|
data = append(data, NewResponseServiceRequest(sr))
|
||||||
}
|
}
|
||||||
if err := render.RenderList(w, r, data); err != nil {
|
if err := render.RenderList(w, r, data); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ func AddRoutes(r chi.Router) {
|
||||||
r.Method("GET", "/service-request", auth.NewEnsureAuth(apiServiceRequest))
|
r.Method("GET", "/service-request", auth.NewEnsureAuth(apiServiceRequest))
|
||||||
r.Method("GET", "/trap-data", auth.NewEnsureAuth(apiTrapData))
|
r.Method("GET", "/trap-data", auth.NewEnsureAuth(apiTrapData))
|
||||||
r.Method("GET", "/client/ios", auth.NewEnsureAuth(handleClientIos))
|
r.Method("GET", "/client/ios", auth.NewEnsureAuth(handleClientIos))
|
||||||
r.Method("PUT", "/client/ios/note/{uuid}", auth.NewEnsureAuth(apiClientIosNotePut))
|
|
||||||
r.Method("POST", "/audio/{uuid}", auth.NewEnsureAuth(apiAudioPost))
|
r.Method("POST", "/audio/{uuid}", auth.NewEnsureAuth(apiAudioPost))
|
||||||
r.Method("POST", "/audio/{uuid}/content", auth.NewEnsureAuth(apiAudioContentPost))
|
r.Method("POST", "/audio/{uuid}/content", auth.NewEnsureAuth(apiAudioContentPost))
|
||||||
r.Method("POST", "/image/{uuid}", auth.NewEnsureAuth(apiImagePost))
|
r.Method("POST", "/image/{uuid}", auth.NewEnsureAuth(apiImagePost))
|
||||||
|
|
|
||||||
87
api/types.go
87
api/types.go
|
|
@ -32,44 +32,34 @@ func NewBounds() Bounds {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type LatLong interface {
|
/* not sure if used
|
||||||
Latitude() float64
|
|
||||||
Longitude() float64
|
|
||||||
}
|
|
||||||
|
|
||||||
type Location struct {
|
type Location struct {
|
||||||
Latitude float64
|
Latitude float64
|
||||||
Longitude float64
|
Longitude float64
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
type NoteImagePayload struct {
|
type NoteImagePayload struct {
|
||||||
UUID string `json:"uuid"`
|
UUID string `json:"uuid"`
|
||||||
Cell H3Cell `json:"cell"`
|
Cell H3Cell `json:"cell"`
|
||||||
Created time.Time `json:"created"`
|
Created time.Time `json:"created"`
|
||||||
}
|
CreatorID int `db:"creator_id"`
|
||||||
|
Deleted *time.Time `json:"deleted"`
|
||||||
type NoteAudio struct {
|
DeletorID *int32 `json:"deletor_id"`
|
||||||
UUID string `db:"uuid"`
|
Version int32 `json:"version"`
|
||||||
Breadcrumbs []NoteAudioBreadcrumbPayload
|
|
||||||
Created time.Time `db:"created"`
|
|
||||||
Creator int `db:"creator"`
|
|
||||||
Deleted *time.Time `db:"deleted"`
|
|
||||||
Duration int `db:"duration"`
|
|
||||||
IsAudioNormalized bool `db:"is_audio_normalized"`
|
|
||||||
IsTranscodedeToOgg bool `db:"is_transcoded_to_ogg"`
|
|
||||||
Transcription *string `db:"transcription"`
|
|
||||||
TranscriptionUserEdited bool `db:"transcription_user_edited"`
|
|
||||||
Version int `db:"version"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type NoteAudioPayload struct {
|
type NoteAudioPayload struct {
|
||||||
UUID string `json:"uuid"`
|
UUID string `json:"uuid"`
|
||||||
Breadcrumbs []NoteAudioBreadcrumbPayload `json:"breadcrumbs"`
|
Breadcrumbs []NoteAudioBreadcrumbPayload `json:"breadcrumbs"`
|
||||||
Created time.Time `json:"created"`
|
Created time.Time `json:"created"`
|
||||||
Duration int `json:"duration"`
|
CreatorID int `json:"creator_id"`
|
||||||
|
Deleted *time.Time `json:"deleted"`
|
||||||
|
DeletorID *int32 `json:"deletor_id"`
|
||||||
|
Duration float32 `json:"duration"`
|
||||||
Transcription *string `json:"transcription"`
|
Transcription *string `json:"transcription"`
|
||||||
TranscriptionUserEdited bool `json:"transcriptionUserEdited"`
|
TranscriptionUserEdited bool `json:"transcriptionUserEdited"`
|
||||||
Version int `json:"version"`
|
Version int32 `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResponseMosquitoSource struct {
|
type ResponseMosquitoSource struct {
|
||||||
|
|
@ -97,14 +87,6 @@ type NoteAudioBreadcrumbPayload struct {
|
||||||
ManuallySelected bool `json:"manuallySelected"`
|
ManuallySelected bool `json:"manuallySelected"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NidusNotePayload struct {
|
|
||||||
UUID string `json:"uuid"`
|
|
||||||
Timestamp time.Time `json:"timestamp"`
|
|
||||||
H3Cell int64 `json:"h3cell"`
|
|
||||||
Images []string `json:"images"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResponseFieldseeker struct {
|
type ResponseFieldseeker struct {
|
||||||
MosquitoSources []ResponseMosquitoSource `json:"sources"`
|
MosquitoSources []ResponseMosquitoSource `json:"sources"`
|
||||||
ServiceRequests []ResponseServiceRequest `json:"requests"`
|
ServiceRequests []ResponseServiceRequest `json:"requests"`
|
||||||
|
|
@ -114,6 +96,7 @@ type ResponseFieldseeker struct {
|
||||||
// ResponseErr renderer type for handling all sorts of errors.
|
// ResponseErr renderer type for handling all sorts of errors.
|
||||||
type ResponseClientIos struct {
|
type ResponseClientIos struct {
|
||||||
Fieldseeker ResponseFieldseeker `json:"fieldseeker"`
|
Fieldseeker ResponseFieldseeker `json:"fieldseeker"`
|
||||||
|
Since time.Time `json:"since"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i ResponseClientIos) Render(w http.ResponseWriter, r *http.Request) error {
|
func (i ResponseClientIos) Render(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
|
@ -163,9 +146,9 @@ func NewResponseMosquitoInspection(i *models.FieldseekerMosquitoinspection) Resp
|
||||||
SiteCondition: i.Sitecond.GetOr(""),
|
SiteCondition: i.Sitecond.GetOr(""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func NewResponseMosquitoInspections(inspections *models.FieldseekerMosquitoinspectionSlice) []ResponseMosquitoInspection {
|
func NewResponseMosquitoInspections(inspections models.FieldseekerMosquitoinspectionSlice) []ResponseMosquitoInspection {
|
||||||
results := make([]ResponseMosquitoInspection, 0)
|
results := make([]ResponseMosquitoInspection, 0)
|
||||||
for _, i := range *inspections {
|
for _, i := range inspections {
|
||||||
results = append(results, NewResponseMosquitoInspection(i))
|
results = append(results, NewResponseMosquitoInspection(i))
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
|
|
@ -175,7 +158,7 @@ func (rtd ResponseMosquitoSource) Render(w http.ResponseWriter, r *http.Request)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResponseMosquitoSource(ms *platform.MosquitoSource) ResponseMosquitoSource {
|
func NewResponseMosquitoSource(ms platform.MosquitoSource) ResponseMosquitoSource {
|
||||||
pl := ms.PointLocation
|
pl := ms.PointLocation
|
||||||
return ResponseMosquitoSource{
|
return ResponseMosquitoSource{
|
||||||
Active: toBool16(pl.Active),
|
Active: toBool16(pl.Active),
|
||||||
|
|
@ -196,9 +179,9 @@ func NewResponseMosquitoSource(ms *platform.MosquitoSource) ResponseMosquitoSour
|
||||||
Zone: pl.Zone.GetOr(""),
|
Zone: pl.Zone.GetOr(""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func NewResponseMosquitoSources(sources *[]*platform.MosquitoSource) []ResponseMosquitoSource {
|
func NewResponseMosquitoSources(sources []platform.MosquitoSource) []ResponseMosquitoSource {
|
||||||
results := make([]ResponseMosquitoSource, 0)
|
results := make([]ResponseMosquitoSource, 0)
|
||||||
for _, i := range *sources {
|
for _, i := range sources {
|
||||||
results = append(results, NewResponseMosquitoSource(i))
|
results = append(results, NewResponseMosquitoSource(i))
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
|
|
@ -224,24 +207,22 @@ func (rtd ResponseMosquitoTreatment) Render(w http.ResponseWriter, r *http.Reque
|
||||||
}
|
}
|
||||||
func NewResponseMosquitoTreatment(i *models.FieldseekerTreatment) ResponseMosquitoTreatment {
|
func NewResponseMosquitoTreatment(i *models.FieldseekerTreatment) ResponseMosquitoTreatment {
|
||||||
return ResponseMosquitoTreatment{
|
return ResponseMosquitoTreatment{
|
||||||
/*
|
Comments: i.Comments.GetOr(""),
|
||||||
Comments: i.Comments(),
|
Created: formatTime(i.Creationdate),
|
||||||
Created: i.Created().Format("2006-01-02T15:04:05.000Z"),
|
FieldTechnician: i.Fieldtech.GetOr(""),
|
||||||
FieldTechnician: i.FieldTechnician(),
|
Habitat: i.Habitat.GetOr(""),
|
||||||
Habitat: i.Habitat(),
|
ID: i.Globalid.String(),
|
||||||
ID: i.ID(),
|
Product: i.Product.GetOr(""),
|
||||||
Product: i.Product(),
|
Quantity: i.Qty.GetOr(0),
|
||||||
Quantity: i.Quantity(),
|
QuantityUnit: i.Qtyunit.GetOr(""),
|
||||||
QuantityUnit: i.QuantityUnit(),
|
SiteCondition: i.Sitecond.GetOr(""),
|
||||||
SiteCondition: i.SiteCondition(),
|
TreatAcres: i.Treatacres.GetOr(0.0),
|
||||||
TreatAcres: i.TreatAcres(),
|
TreatHectares: i.Treathectares.GetOr(0.0),
|
||||||
TreatHectares: i.TreatHectares(),
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func NewResponseMosquitoTreatments(treatments *models.FieldseekerTreatmentSlice) []ResponseMosquitoTreatment {
|
func NewResponseMosquitoTreatments(treatments models.FieldseekerTreatmentSlice) []ResponseMosquitoTreatment {
|
||||||
results := make([]ResponseMosquitoTreatment, 0)
|
results := make([]ResponseMosquitoTreatment, 0)
|
||||||
for _, i := range *treatments {
|
for _, i := range treatments {
|
||||||
results = append(results, NewResponseMosquitoTreatment(i))
|
results = append(results, NewResponseMosquitoTreatment(i))
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
|
|
@ -298,9 +279,9 @@ func NewResponseServiceRequest(sr *models.FieldseekerServicerequest) ResponseSer
|
||||||
Zip: sr.Reqzip.GetOr(""),
|
Zip: sr.Reqzip.GetOr(""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func NewResponseServiceRequests(requests *models.FieldseekerServicerequestSlice) []ResponseServiceRequest {
|
func NewResponseServiceRequests(requests models.FieldseekerServicerequestSlice) []ResponseServiceRequest {
|
||||||
results := make([]ResponseServiceRequest, 0)
|
results := make([]ResponseServiceRequest, 0)
|
||||||
for _, i := range *requests {
|
for _, i := range requests {
|
||||||
results = append(results, NewResponseServiceRequest(i))
|
results = append(results, NewResponseServiceRequest(i))
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
|
|
@ -326,9 +307,9 @@ func NewResponseTrapDatum(td *models.FieldseekerTraplocation) ResponseTrapData {
|
||||||
Name: td.Name.GetOr(""),
|
Name: td.Name.GetOr(""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func NewResponseTrapData(data *models.FieldseekerTraplocationSlice) []ResponseTrapData {
|
func NewResponseTrapData(data models.FieldseekerTraplocationSlice) []ResponseTrapData {
|
||||||
results := make([]ResponseTrapData, 0)
|
results := make([]ResponseTrapData, 0)
|
||||||
for _, i := range *data {
|
for _, i := range data {
|
||||||
results = append(results, NewResponseTrapDatum(i))
|
results = append(results, NewResponseTrapDatum(i))
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,7 @@ func findUser(ctx context.Context, user_id int) (*models.User, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.Info().Int32("user_id", user.ID).Int32("org_id", user.OrganizationID).Msg("Found user")
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
37
db/query.go
37
db/query.go
|
|
@ -3,20 +3,25 @@ package db
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
type NidusNotePayload struct {}
|
|
||||||
type NoteAudio struct {
|
|
||||||
Transcription string
|
|
||||||
Version int
|
|
||||||
UUID uuid.UUID
|
|
||||||
}
|
|
||||||
type NoteImage struct {}
|
|
||||||
|
|
||||||
func NoteAudioCreate(ctx context.Context, noteUUID uuid.UUID, payload NoteAudio, userID int32) error {
|
func NoteAudioCreate(ctx context.Context, org *models.Organization, userID int32, setter models.NoteAudioSetter) error {
|
||||||
|
err := org.InsertNoteAudios(ctx, PGInstance.BobDB, &setter)
|
||||||
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func NoteAudioGetLatest(ctx context.Context, uuid string) (*NoteAudio, error) {
|
// Just ignore this failure, it means we already have this content
|
||||||
|
if err.Error() == "insertOrganizationNoteAudios0: ERROR: duplicate key value violates unique constraint \"note_audio_pkey\" (SQLSTATE 23505)" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Warn().Err(err).Msg("Unrecognized error creating note audio")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func NoteAudioGetLatest(ctx context.Context, uuid string) (*models.NoteAudio, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func NoteAudioNormalized(uuid string) error {
|
func NoteAudioNormalized(uuid string) error {
|
||||||
|
|
@ -25,9 +30,19 @@ func NoteAudioNormalized(uuid string) error {
|
||||||
func NoteAudioTranscodedToOgg(uuid string) error {
|
func NoteAudioTranscodedToOgg(uuid string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func NoteImageCreate(ctx context.Context, noteUUID uuid.UUID, payload NoteImage, userID int32) error {
|
func NoteImageCreate(ctx context.Context, org *models.Organization, userID int32, setter models.NoteImageSetter) error {
|
||||||
|
err := org.InsertNoteImages(ctx, PGInstance.BobDB, &setter)
|
||||||
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func NoteUpdate(ctx context.Context, noteUUID uuid.UUID, payload NidusNotePayload) error {
|
// Just ignore this failure, it means we already have this content
|
||||||
|
if err.Error() == "insertOrganizationNoteImages0: ERROR: duplicate key value violates unique constraint \"note_image_pkey\" (SQLSTATE 23505)" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Warn().Err(err).Msg("Unrecognized error creating note audio")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func NoteUpdate(ctx context.Context, noteUUID uuid.UUID, setter models.NoteAudioSetter) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
main.go
7
main.go
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/api"
|
"github.com/Gleipnir-Technology/nidus-sync/api"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/auth"
|
"github.com/Gleipnir-Technology/nidus-sync/auth"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||||
|
"github.com/Gleipnir-Technology/nidus-sync/queue"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/userfile"
|
"github.com/Gleipnir-Technology/nidus-sync/userfile"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
|
@ -162,6 +163,12 @@ func main() {
|
||||||
refreshFieldseekerData(ctx, NewOAuthTokenChannel)
|
refreshFieldseekerData(ctx, NewOAuthTokenChannel)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
waitGroup.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer waitGroup.Done()
|
||||||
|
queue.StartAudioWorker(ctx)
|
||||||
|
}()
|
||||||
|
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: bind,
|
Addr: bind,
|
||||||
Handler: r,
|
Handler: r,
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,17 @@ import (
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MosquitoSourceQuery() (*[]*MosquitoSource, error) {
|
func MosquitoSourceQuery() ([]MosquitoSource, error) {
|
||||||
results := make([]*MosquitoSource, 0)
|
results := make([]MosquitoSource, 0)
|
||||||
return &results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServiceRequestQuery() (*models.FieldseekerServicerequestSlice, error) {
|
func ServiceRequestQuery() (models.FieldseekerServicerequestSlice, error) {
|
||||||
results := make(models.FieldseekerServicerequestSlice, 0)
|
results := make(models.FieldseekerServicerequestSlice, 0)
|
||||||
return &results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TrapDataQuery() (*models.FieldseekerTraplocationSlice, error) {
|
func TrapDataQuery() (models.FieldseekerTraplocationSlice, error) {
|
||||||
results := make(models.FieldseekerTraplocationSlice, 0)
|
results := make(models.FieldseekerTraplocationSlice, 0)
|
||||||
return &results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ func fieldseeker(ctx context.Context, u *models.User, since *time.Time) (fsync F
|
||||||
ts = append(ts, t)
|
ts = append(ts, t)
|
||||||
treatments_by_location[locid] = ts
|
treatments_by_location[locid] = ts
|
||||||
}
|
}
|
||||||
sources := make([]*MosquitoSource, 0)
|
sources := make([]MosquitoSource, 0)
|
||||||
for _, p := range pl {
|
for _, p := range pl {
|
||||||
inspections, ok := inspections_by_location[p.Globalid]
|
inspections, ok := inspections_by_location[p.Globalid]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -68,13 +68,13 @@ func fieldseeker(ctx context.Context, u *models.User, since *time.Time) (fsync F
|
||||||
treatments = make(models.FieldseekerTreatmentSlice, 0)
|
treatments = make(models.FieldseekerTreatmentSlice, 0)
|
||||||
}
|
}
|
||||||
ms := MosquitoSource{
|
ms := MosquitoSource{
|
||||||
PointLocation: p,
|
PointLocation: *p,
|
||||||
Inspections: &inspections,
|
Inspections: inspections,
|
||||||
Treatments: &treatments,
|
Treatments: treatments,
|
||||||
}
|
}
|
||||||
sources = append(sources, &ms)
|
sources = append(sources, ms)
|
||||||
}
|
}
|
||||||
fsync.MosquitoSources = &sources
|
fsync.MosquitoSources = sources
|
||||||
return fsync, err
|
return fsync, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,13 @@ type ClientSync struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type FieldseekerRecordsSync struct {
|
type FieldseekerRecordsSync struct {
|
||||||
MosquitoSources *[]*MosquitoSource
|
MosquitoSources []MosquitoSource
|
||||||
ServiceRequests *models.FieldseekerServicerequestSlice
|
ServiceRequests models.FieldseekerServicerequestSlice
|
||||||
TrapData *models.FieldseekerTraplocationSlice
|
TrapData models.FieldseekerTraplocationSlice
|
||||||
}
|
}
|
||||||
|
|
||||||
type MosquitoSource struct {
|
type MosquitoSource struct {
|
||||||
PointLocation *models.FieldseekerPointlocation
|
PointLocation models.FieldseekerPointlocation
|
||||||
Inspections *models.FieldseekerMosquitoinspectionSlice
|
Inspections models.FieldseekerMosquitoinspectionSlice
|
||||||
Treatments *models.FieldseekerTreatmentSlice
|
Treatments models.FieldseekerTreatmentSlice
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/userfile"
|
"github.com/Gleipnir-Technology/nidus-sync/userfile"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AudioJob represents a job to process an audio file.
|
// AudioJob represents a job to process an audio file.
|
||||||
|
|
@ -25,18 +25,18 @@ var audioJobChannel chan AudioJob
|
||||||
func StartAudioWorker(ctx context.Context) {
|
func StartAudioWorker(ctx context.Context) {
|
||||||
buffer := 100
|
buffer := 100
|
||||||
audioJobChannel = make(chan AudioJob, buffer) // Buffered channel to prevent blocking
|
audioJobChannel = make(chan AudioJob, buffer) // Buffered channel to prevent blocking
|
||||||
log.Printf("Started audio worker with buffer depth %d", buffer)
|
log.Info().Int("buffer depth", buffer).Msg("Started audio worker")
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
log.Println("Audio worker shutting down.")
|
log.Info().Msg("Audio worker shutting down.")
|
||||||
return
|
return
|
||||||
case job := <-audioJobChannel:
|
case job := <-audioJobChannel:
|
||||||
log.Printf("Processing audio job for UUID: %s", job.AudioUUID)
|
log.Info().Str("uuid", job.AudioUUID.String()).Msg("Processing audio job")
|
||||||
err := processAudioFile(job.AudioUUID)
|
err := processAudioFile(job.AudioUUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error processing audio file %s: %v", job.AudioUUID, err)
|
log.Error().Err(err).Str("uuid", job.AudioUUID.String()).Msg("Error processing audio file")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -47,9 +47,9 @@ func StartAudioWorker(ctx context.Context) {
|
||||||
func EnqueueAudioJob(job AudioJob) {
|
func EnqueueAudioJob(job AudioJob) {
|
||||||
select {
|
select {
|
||||||
case audioJobChannel <- job:
|
case audioJobChannel <- job:
|
||||||
log.Printf("Enqueued audio job for UUID: %s", job.AudioUUID)
|
log.Info().Str("uuid", job.AudioUUID.String()).Msg("Enqueued audio job")
|
||||||
default:
|
default:
|
||||||
log.Printf("Audio job channel is full, dropping job for UUID: %s", job.AudioUUID)
|
log.Warn().Str("uuid", job.AudioUUID.String()).Msg("Audio job channel is full, dropping job")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,10 +76,10 @@ func normalizeAudio(audioUUID uuid.UUID) error {
|
||||||
source := userfile.AudioFileContentPathRaw(audioUUID.String())
|
source := userfile.AudioFileContentPathRaw(audioUUID.String())
|
||||||
_, err := os.Stat(source)
|
_, err := os.Stat(source)
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
log.Printf("%s doesn't exist, skipping normalization", source)
|
log.Warn().Str("source", source).Msg("file doesn't exist, skipping normalization")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Printf("Normalizing %s", source)
|
log.Info().Str("sourcce", source).Msg("Normalizing")
|
||||||
destination := userfile.AudioFileContentPathNormalized(audioUUID.String())
|
destination := userfile.AudioFileContentPathNormalized(audioUUID.String())
|
||||||
// Use "ffmpeg" directly, assuming it's in the system PATH
|
// Use "ffmpeg" directly, assuming it's in the system PATH
|
||||||
cmd := exec.Command("ffmpeg", "-i", source, "-filter:a", "loudnorm", destination)
|
cmd := exec.Command("ffmpeg", "-i", source, "-filter:a", "loudnorm", destination)
|
||||||
|
|
@ -92,7 +92,7 @@ func normalizeAudio(audioUUID uuid.UUID) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update database for normalized audio %s: %v", audioUUID, err)
|
return fmt.Errorf("failed to update database for normalized audio %s: %v", audioUUID, err)
|
||||||
}
|
}
|
||||||
log.Printf("Normalized audio to %s", destination)
|
log.Info().Str("destination", destination).Msg("Normalized audio")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,22 +100,22 @@ func transcodeToOgg(audioUUID uuid.UUID) error {
|
||||||
source := userfile.AudioFileContentPathNormalized(audioUUID.String())
|
source := userfile.AudioFileContentPathNormalized(audioUUID.String())
|
||||||
_, err := os.Stat(source)
|
_, err := os.Stat(source)
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
log.Printf("%s doesn't exist, skipping OGG transcoding", source)
|
log.Warn().Str("source", source).Msg("file doesn't exist, skipping OGG transcoding")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Printf("Transcoding %s to ogg", source)
|
log.Info().Str("source", source).Msg("Transcoding to ogg")
|
||||||
destination := userfile.AudioFileContentPathOgg(audioUUID.String())
|
destination := userfile.AudioFileContentPathOgg(audioUUID.String())
|
||||||
// Use "ffmpeg" directly, assuming it's in the system PATH
|
// Use "ffmpeg" directly, assuming it's in the system PATH
|
||||||
cmd := exec.Command("ffmpeg", "-i", source, "-vn", "-acodec", "libvorbis", destination)
|
cmd := exec.Command("ffmpeg", "-i", source, "-vn", "-acodec", "libvorbis", destination)
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("FFmpeg output for OGG transcoding: %s", out)
|
log.Error().Err(err).Bytes("out", out).Msg("FFmpeg output for OGG transcoding")
|
||||||
return fmt.Errorf("ffmpeg OGG transcoding failed: %v", err)
|
return fmt.Errorf("ffmpeg OGG transcoding failed: %v", err)
|
||||||
}
|
}
|
||||||
err = db.NoteAudioTranscodedToOgg(audioUUID.String())
|
err = db.NoteAudioTranscodedToOgg(audioUUID.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update database for OGG transcoded audio %s: %v", audioUUID, err)
|
return fmt.Errorf("failed to update database for OGG transcoded audio %s: %v", audioUUID, err)
|
||||||
}
|
}
|
||||||
log.Printf("Transcoded audio to %s", destination)
|
log.Info().Str("destination", destination).Msg("Transcoded audio")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||||
|
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/label-studio"
|
"github.com/Gleipnir-Technology/nidus-sync/label-studio"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/minio"
|
"github.com/Gleipnir-Technology/nidus-sync/minio"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/userfile"
|
"github.com/Gleipnir-Technology/nidus-sync/userfile"
|
||||||
|
|
@ -127,7 +128,7 @@ func processLabelTask(ctx context.Context, minioClient *minio.Client, minioBucke
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTask(client *labelstudio.Client, project *labelstudio.Project, minioClient *minio.Client, bucket string, customer string, note *db.NoteAudio) error {
|
func createTask(client *labelstudio.Client, project *labelstudio.Project, minioClient *minio.Client, bucket string, customer string, note *models.NoteAudio) error {
|
||||||
audioRef := fmt.Sprintf("s3://%s/%s-normalized.m4a", bucket, note.UUID)
|
audioRef := fmt.Sprintf("s3://%s/%s-normalized.m4a", bucket, note.UUID)
|
||||||
audioFile := fmt.Sprintf("%s/%s-normalized.m4a", userfile.UserFilesDirectory, note.UUID)
|
audioFile := fmt.Sprintf("%s/%s-normalized.m4a", userfile.UserFilesDirectory, note.UUID)
|
||||||
uploadPath := fmt.Sprintf("%s-normalized.m4a", note.UUID)
|
uploadPath := fmt.Sprintf("%s-normalized.m4a", note.UUID)
|
||||||
|
|
@ -142,7 +143,7 @@ func createTask(client *labelstudio.Client, project *labelstudio.Project, minioC
|
||||||
//if note.Transcription.IsValue() {
|
//if note.Transcription.IsValue() {
|
||||||
//transcription = note.Transcription.MustGet()
|
//transcription = note.Transcription.MustGet()
|
||||||
//}
|
//}
|
||||||
transcription = note.Transcription
|
transcription = note.Transcription.GetOr("")
|
||||||
simpleTasks := []map[string]interface{}{
|
simpleTasks := []map[string]interface{}{
|
||||||
{
|
{
|
||||||
"data": map[string]string{
|
"data": map[string]string{
|
||||||
|
|
@ -184,7 +185,7 @@ func findLabelStudioProject(client *labelstudio.Client, title string) (*labelstu
|
||||||
return nil, fmt.Errorf("No such project '%s'", title)
|
return nil, fmt.Errorf("No such project '%s'", title)
|
||||||
}
|
}
|
||||||
|
|
||||||
func findMatchingTask(client *labelstudio.Client, project *labelstudio.Project, customer string, note *db.NoteAudio) (*labelstudio.Task, error) {
|
func findMatchingTask(client *labelstudio.Client, project *labelstudio.Project, customer string, note *models.NoteAudio) (*labelstudio.Task, error) {
|
||||||
/*meta := map[string]string{
|
/*meta := map[string]string{
|
||||||
"customer": customer,
|
"customer": customer,
|
||||||
"note_uuid": note.UUID,
|
"note_uuid": note.UUID,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue