2026-01-29 23:55:41 +00:00
package rmo
2026-01-16 14:52:11 +00:00
import (
"bytes"
"context"
"fmt"
"image"
_ "image/gif" // register GIF format
_ "image/jpeg" // register JPEG format
_ "image/png" // register PNG format
"io"
"mime/multipart"
"net/http"
"time"
2026-01-27 18:44:02 +00:00
"github.com/Gleipnir-Technology/bob"
"github.com/Gleipnir-Technology/bob/dialect/psql"
"github.com/Gleipnir-Technology/bob/dialect/psql/um"
2026-01-16 14:52:11 +00:00
"github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/userfile"
2026-01-22 03:27:32 +00:00
"github.com/aarondl/opt/omit"
"github.com/aarondl/opt/omitnull"
2026-01-16 14:52:11 +00:00
"github.com/google/uuid"
"github.com/rs/zerolog/log"
2026-01-22 03:27:32 +00:00
"github.com/rwcarlsen/goexif/exif"
//exif "github.com/rwcarlsen/goexif/exif"
//"github.com/dsoprea/go-exif-extra/format"
2026-01-16 14:52:11 +00:00
)
2026-01-22 03:27:32 +00:00
type GPS struct {
Latitude float64
Longitude float64
}
2026-01-16 14:52:11 +00:00
type ExifCollection struct {
2026-01-22 03:27:32 +00:00
GPS * GPS
2026-01-16 14:52:11 +00:00
Tags map [ string ] string
}
type ImageUpload struct {
Bounds image . Rectangle
ContentType string
2026-02-05 21:43:29 +00:00
Exif * ExifCollection
2026-01-16 14:52:11 +00:00
Format string
UploadFilesize int
UploadFilename string
UUID uuid . UUID
}
2026-02-05 21:43:29 +00:00
func extractExif ( content_type string , file_bytes [ ] byte ) ( result * ExifCollection , err error ) {
2026-01-22 03:27:32 +00:00
/ *
Using "github.com/evanoberholster/imagemeta"
meta , err := imagemeta . Decode ( bytes . NewReader ( file_bytes ) )
if err != nil {
return result , fmt . Errorf ( "Failed to decode image meta: %w" , err )
}
result . GPS = & GPS {
Latitude : meta . GPS . Latitude ( ) ,
Longitude : meta . GPS . Longitude ( ) ,
}
return result , err
* /
2026-01-16 14:52:11 +00:00
2026-01-22 03:27:32 +00:00
exif , err := exif . Decode ( bytes . NewReader ( file_bytes ) )
2026-01-16 14:52:11 +00:00
if err != nil {
2026-02-05 21:43:29 +00:00
if err . Error ( ) == "exif: failed to find exif intro marker" {
return nil , nil
}
return nil , fmt . Errorf ( "Failed to decode image meta: %w" , err )
2026-01-16 14:52:11 +00:00
}
2026-01-22 03:27:32 +00:00
lat , lng , _ := exif . LatLong ( )
result . GPS = & GPS {
Latitude : lat ,
Longitude : lng ,
2026-01-16 14:52:11 +00:00
}
2026-01-22 03:27:32 +00:00
return result , err
2026-01-16 14:52:11 +00:00
}
func extractImageUpload ( headers * multipart . FileHeader ) ( upload ImageUpload , err error ) {
file , err := headers . Open ( )
if err != nil {
return upload , fmt . Errorf ( "Failed to open header: %w" , err )
}
defer file . Close ( )
file_bytes , err := io . ReadAll ( file )
content_type := http . DetectContentType ( file_bytes )
2026-01-22 03:27:32 +00:00
exif , err := extractExif ( content_type , file_bytes )
2026-01-16 14:52:11 +00:00
if err != nil {
2026-02-05 21:43:29 +00:00
return upload , fmt . Errorf ( "Failed to extract EXIF data: %w" , err )
2026-01-16 14:52:11 +00:00
}
i , format , err := image . Decode ( bytes . NewReader ( file_bytes ) )
if err != nil {
return upload , fmt . Errorf ( "Failed to decode image file: %w" , err )
}
u , err := uuid . NewUUID ( )
if err != nil {
return upload , fmt . Errorf ( "Failed to create quick report photo uuid" , err )
}
err = userfile . PublicImageFileContentWrite ( u , bytes . NewReader ( file_bytes ) )
if err != nil {
return upload , fmt . Errorf ( "Failed to write image file to disk: %w" , err )
}
log . Info ( ) . Int ( "size" , len ( file_bytes ) ) . Str ( "uploaded_filename" , headers . Filename ) . Str ( "content-type" , content_type ) . Str ( "uuid" , u . String ( ) ) . Msg ( "Saved an uploaded file to disk" )
return ImageUpload {
Bounds : i . Bounds ( ) ,
ContentType : content_type ,
Exif : exif ,
Format : format ,
UploadFilename : headers . Filename ,
UploadFilesize : len ( file_bytes ) ,
UUID : u ,
} , nil
}
func extractImageUploads ( r * http . Request ) ( uploads [ ] ImageUpload , err error ) {
uploads = make ( [ ] ImageUpload , 0 )
for _ , fheaders := range r . MultipartForm . File {
for _ , headers := range fheaders {
upload , err := extractImageUpload ( headers )
if err != nil {
return make ( [ ] ImageUpload , 0 ) , fmt . Errorf ( "Failed to extract photo upload: %w" , err )
}
uploads = append ( uploads , upload )
}
}
return uploads , nil
}
func saveImageUploads ( ctx context . Context , tx bob . Tx , uploads [ ] ImageUpload ) ( models . PublicreportImageSlice , error ) {
images := make ( models . PublicreportImageSlice , 0 )
for _ , u := range uploads {
image , err := models . PublicreportImages . Insert ( & models . PublicreportImageSetter {
ContentType : omit . From ( u . ContentType ) ,
Created : omit . From ( time . Now ( ) ) ,
2026-01-22 03:27:32 +00:00
//Location: psql.Raw("NULL"),
Location : omitnull . FromPtr [ string ] ( nil ) ,
2026-01-16 14:52:11 +00:00
ResolutionX : omit . From ( int32 ( u . Bounds . Max . X ) ) ,
ResolutionY : omit . From ( int32 ( u . Bounds . Max . Y ) ) ,
StorageUUID : omit . From ( u . UUID ) ,
StorageSize : omit . From ( int64 ( u . UploadFilesize ) ) ,
UploadedFilename : omit . From ( u . UploadFilename ) ,
} ) . One ( ctx , tx )
if err != nil {
return images , fmt . Errorf ( "Failed to create photo records: %w" , err )
}
// TODO: figure out how to do this via the setter...?
2026-02-05 21:43:29 +00:00
if u . Exif != nil {
if u . Exif . GPS != nil {
_ , err = psql . Update (
um . Table ( "publicreport.image" ) ,
um . SetCol ( "location" ) . To ( fmt . Sprintf ( "ST_GeometryFromText('Point(%f %f)')" , u . Exif . GPS . Longitude , u . Exif . GPS . Latitude ) ) ,
um . Where ( psql . Quote ( "id" ) . EQ ( psql . Arg ( image . ID ) ) ) ,
) . Exec ( ctx , tx )
}
2026-01-16 14:52:11 +00:00
2026-02-05 21:43:29 +00:00
exif_setters := make ( [ ] * models . PublicreportImageExifSetter , 0 )
for k , v := range u . Exif . Tags {
exif_setters = append ( exif_setters , & models . PublicreportImageExifSetter {
ImageID : omit . From ( image . ID ) ,
Name : omit . From ( k ) ,
Value : omit . From ( v ) ,
} )
}
if len ( exif_setters ) > 0 {
_ , err = models . PublicreportImageExifs . Insert ( bob . ToMods ( exif_setters ... ) ) . Exec ( ctx , tx )
if err != nil {
return images , fmt . Errorf ( "Failed to create photo exif records: %w" , err )
}
2026-01-22 03:27:32 +00:00
}
2026-02-05 21:43:29 +00:00
log . Info ( ) . Int32 ( "id" , image . ID ) . Int ( "tags" , len ( u . Exif . Tags ) ) . Msg ( "Saved an uploaded file to the database" )
} else {
log . Info ( ) . Int32 ( "id" , image . ID ) . Int ( "tags" , 0 ) . Msg ( "Saved an uploaded file without EXIF data" )
2026-01-16 14:52:11 +00:00
}
images = append ( images , image )
}
return images , nil
}