package loads import ( "context" "fmt" "git.siteworxpro.com/reloading-manager/backend/.gen/loading/public/table" "git.siteworxpro.com/reloading-manager/backend/database" "git.siteworxpro.com/reloading-manager/backend/handlers" "git.siteworxpro.com/reloading-manager/backend/handlers/bullets" "git.siteworxpro.com/reloading-manager/backend/handlers/primers" "git.siteworxpro.com/reloading-manager/backend/models/loads" "github.com/go-jet/jet/v2/postgres" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" "github.com/labstack/echo/v4" "net/http" "strings" ) type row struct { ID pgtype.UUID Col float32 PowderGr float32 CartridgeID pgtype.UUID CartridgeName string CartridgeMeta []byte BulletID pgtype.UUID BulletName string BulletDiameter int32 BulletWeight int32 BulletMeta []byte BulletManufacturerName string BulletManufacturerUrl pgtype.Text PrimerID pgtype.UUID PrimerName string PrimerMeta []byte PrimerManufacturerName string PrimerManufacturerUrl pgtype.Text PowderID pgtype.UUID PowderName string PowderMeta []byte PowderManufacturerName string PowderManufacturerUrl pgtype.Text } type loadResponseResults struct { Id string `json:"id"` Cartridge string `json:"cartridge"` CartridgeId string `json:"cartridge_id"` Col float32 `json:"col"` Powder handlers.Powder `json:"powder"` PowderGr float32 `json:"powder_gr"` Primer primers.PrimerResponse `json:"primer"` Bullet bullets.BulletResponse `json:"bullet"` } type loadResponse struct { Total int `json:"total"` Results []loadResponseResults `json:"results"` } type ResultChan[T any] struct { Result T Err error } func Post(c echo.Context) error { db := c.(*database.CustomContext).Db defer db.Db.Close(context.Background()) cartridgeID, err := handlers.ParseUuid(c.FormValue("cartridge_id")) if err != nil { return handlers.BadRequest(c, "cartridge_id is not a valid UUID") } powderId, err := handlers.ParseUuid(c.FormValue("powder_id")) if err != nil { return handlers.BadRequest(c, "powder_id is not a valid UUID") } primerId, err := handlers.ParseUuid(c.FormValue("primer_id")) if err != nil { return handlers.BadRequest(c, "primer_id is not a valid UUID") } bulletId, err := handlers.ParseUuid(c.FormValue("bullet_id")) if err != nil { return handlers.BadRequest(c, "bullet_id is not a valid UUID") } file, err := handlers.ReadFile(c, "photo") if err != nil { return handlers.BadRequest(c, "photo is not valid") } meta := c.FormValue("meta") if meta == "" { meta = "{}" } powderGr := c.FormValue("powder_gr") if powderGr == "" { return handlers.BadRequest(c, "powder_gr is not valid") } powderGrFl, err := handlers.ParseFloat32(powderGr) if err != nil { return handlers.BadRequest(c, "powder_gr is not valid") } col := c.FormValue("col") if col == "" { return handlers.BadRequest(c, "col is not valid") } colFl, err := handlers.ParseFloat32(col) if err != nil { return handlers.BadRequest(c, "col is not valid") } uid, err := db.Loads.CreateLoad(context.Background(), loads.CreateLoadParams{ CartridgeID: *cartridgeID, Col: colFl, PowderID: *powderId, PowderGr: powderGrFl, PrimerID: *primerId, BulletID: *bulletId, Photo: file, Meta: []byte(meta), }) if err != nil { return err } return c.JSON(http.StatusCreated, handlers.Response[string]{Payload: uid.String()}) } func Get(c echo.Context) error { id := c.Param("id") cResults := make(chan ResultChan[[]loadResponseResults]) if id != "" { go execResultsQuery(cResults, c) results := <-cResults if results.Err != nil { return results.Err } if len(results.Result) == 0 { return handlers.NotFound(c, "load not found") } return c.JSON(http.StatusOK, handlers.Response[loadResponseResults]{Status: http.StatusText(http.StatusOK), Payload: results.Result[0]}) } cTotal := make(chan ResultChan[int64]) go func(ch chan ResultChan[int64]) { db := database.GetNewDatabase() defer func(Db *pgx.Conn, ctx context.Context) { _ = Db.Close(ctx) }(db.Db, context.Background()) q := getQuery(c, true) if q == nil { ch <- ResultChan[int64]{Result: 0} return } sql, params := q.Sql() var total int64 r := db.Db.QueryRow(context.Background(), sql, params...) err := r.Scan(&total) if err != nil { ch <- ResultChan[int64]{Err: err} } else { ch <- ResultChan[int64]{Result: total} } }(cTotal) go execResultsQuery(cResults, c) total := <-cTotal if total.Err != nil { return total.Err } results := <-cResults if results.Err != nil { return results.Err } return c.JSON(http.StatusOK, handlers.Response[loadResponse]{Status: http.StatusText(http.StatusOK), Payload: loadResponse{Total: int(total.Result), Results: results.Result}}) } func execResultsQuery(ch chan ResultChan[[]loadResponseResults], c echo.Context) { db := database.GetNewDatabase() defer func(Db *pgx.Conn, ctx context.Context) { _ = Db.Close(ctx) }(db.Db, context.Background()) q := getQuery(c, false) if q == nil { ch <- ResultChan[[]loadResponseResults]{Result: []loadResponseResults{}} return } fmt.Println(q.DebugSql()) sql, params := q.Sql() rows, err := db.Db.Query(context.Background(), sql, params...) if err != nil { ch <- ResultChan[[]loadResponseResults]{Err: err} return } results := make([]loadResponseResults, 0) for rows.Next() { row := row{} err = rows.Scan( &row.ID, &row.Col, &row.PowderGr, &row.CartridgeID, &row.CartridgeName, &row.CartridgeMeta, &row.BulletID, &row.BulletName, &row.BulletDiameter, &row.BulletWeight, &row.BulletMeta, &row.BulletManufacturerName, &row.BulletManufacturerUrl, &row.PrimerID, &row.PrimerName, &row.PrimerMeta, &row.PrimerManufacturerName, &row.PrimerManufacturerUrl, &row.PowderID, &row.PowderName, &row.PowderMeta, &row.PowderManufacturerName, &row.PowderManufacturerUrl, ) if err != nil { ch <- ResultChan[[]loadResponseResults]{Err: err} return } results = append(results, loadResponseResults{ Id: row.ID.String(), Cartridge: row.CartridgeName, CartridgeId: row.CartridgeID.String(), Col: row.Col, Powder: handlers.Powder{ Id: row.PowderID.String(), Name: row.PowderName, Meta: string(row.PowderMeta), Manufacturer: handlers.Manufacturer{ Name: row.PowderManufacturerName, Url: row.PowderManufacturerUrl.String, }, }, PowderGr: row.PowderGr, Primer: primers.PrimerResponse{ ID: row.PrimerID.String(), Name: row.PrimerName, Manufacturer: handlers.Manufacturer{ Name: row.PrimerManufacturerName, Url: row.PrimerManufacturerUrl.String, }, }, Bullet: bullets.BulletResponse{ Id: row.BulletID.String(), Name: row.BulletName, Diameter: row.BulletDiameter, Weight: row.BulletWeight, Manufacturer: handlers.Manufacturer{ Name: row.BulletManufacturerName, Url: row.BulletManufacturerUrl.String, }, }, }) } ch <- ResultChan[[]loadResponseResults]{Result: results} } func getQuery(c echo.Context, countOnly bool) postgres.SelectStatement { l := table.Loads.AS("l") ctg := table.Cartridges.AS("c") b := table.Bullets.AS("b") bm := table.Manufacturers.AS("b_m") p := table.Primers.AS("p") pm := table.Manufacturers.AS("p_m") pwd := table.Powders.AS("pw") pwdm := table.Manufacturers.AS("pw_m") tb := l. INNER_JOIN(ctg, l.CartridgeID.EQ(ctg.ID)). INNER_JOIN(b, l.BulletID.EQ(b.ID)). INNER_JOIN(bm, b.ManufacturerID.EQ(bm.ID)). INNER_JOIN(p, l.PrimerID.EQ(p.ID)). INNER_JOIN(pm, p.ManufacturerID.EQ(pm.ID)). INNER_JOIN(pwd, l.PowderID.EQ(pwd.ID)). INNER_JOIN(pwdm, pwd.ManufacturerID.EQ(pwdm.ID)) var q postgres.SelectStatement if countOnly { q = tb.SELECT(postgres.COUNT(l.ID).AS("total")) } else { q = tb.SELECT( // Load l.ID.AS("id"), l.Col.AS("col"), l.PowderGr.AS("powder_gr"), // Cartridge ctg.ID.AS("cartridge_id"), ctg.Name.AS("cartridge_name"), ctg.Meta.AS("cartridge_meta"), // Bullet b.ID.AS("bullet_id"), b.Name.AS("bullet_name"), b.Diameter.AS("bullet_diameter"), b.Weight.AS("bullet_weight"), b.Meta.AS("bullet_meta"), bm.Name.AS("bullet_manufacturer_name"), bm.URL.AS("bullet_manufacturer_url"), // Primer p.ID.AS("primer_id"), p.Name.AS("primer_name"), p.Meta.AS("primer_meta"), pm.Name.AS("primer_manufacturer_name"), pm.URL.AS("primer_manufacturer_url"), // Powder pwd.ID.AS("powder_id"), pwd.Name.AS("powder_name"), pwd.Meta.AS("powder_meta"), pwdm.Name.AS("powder_manufacturer_name"), pwdm.URL.AS("powder_manufacturer_url"), ) } // where expressions expressions := make([]postgres.BoolExpression, 0) if c.Param("id") != "" { uuid, err := handlers.ParseUuid(c.Param("id")) if err != nil { return nil } q = q.WHERE(l.ID.EQ(postgres.UUID(uuid))) return q } if c.QueryParam("cartridge_id") != "" { ids := strings.Split(c.QueryParam("cartridge_id"), ",") if len(ids) > 0 { expressions = append(expressions, ctg.ID.IN(getUuidExpr(ids)...)) } } if c.QueryParam("bullet_manufacturer_id") != "" { ids := strings.Split(c.QueryParam("bullet_manufacturer_id"), ",") if len(ids) > 0 { expressions = append(expressions, bm.ID.IN(getUuidExpr(ids)...)) } } if c.QueryParam("bullet_id") != "" { ids := strings.Split(c.QueryParam("bullet_id"), ",") if len(ids) > 0 { expressions = append(expressions, b.ID.IN(getUuidExpr(ids)...)) } } if c.QueryParam("primer_manufacturer_id") != "" { ids := strings.Split(c.QueryParam("primer_manufacturer_id"), ",") if len(ids) > 0 { expressions = append(expressions, pm.ID.IN(getUuidExpr(ids)...)) } } if c.QueryParam("primer_id") != "" { ids := strings.Split(c.QueryParam("primer_id"), ",") if len(ids) > 0 { expressions = append(expressions, p.ID.IN(getUuidExpr(ids)...)) } } if c.QueryParam("powder_manufacturer_id") != "" { ids := strings.Split(c.QueryParam("powder_manufacturer_id"), ",") if len(ids) > 0 { expressions = append(expressions, pwdm.ID.IN(getUuidExpr(ids)...)) } } if c.QueryParam("powder_id") != "" { ids := strings.Split(c.QueryParam("powder_id"), ",") if len(ids) > 0 { expressions = append(expressions, pwd.ID.IN(getUuidExpr(ids)...)) } } if c.QueryParam("search_cartridge") != "" { expressions = append(expressions, ctg.Name.LIKE(postgres.String(c.QueryParam("search_cartridge")+"%"))) } if c.QueryParam("search_bullet") != "" { expressions = append(expressions, b.Name.LIKE(postgres.String(c.QueryParam("search_bullet")+"%"))) } var where postgres.BoolExpression if len(expressions) > 0 { for _, expr := range expressions { if where == nil { where = expr } else { where = where.AND(expr) } } } if where != nil { q = q.WHERE(where) } if countOnly { return q } orderBy := c.QueryParam("sortField") var sort postgres.Column switch orderBy { case "cartridge_name": sort = ctg.Name case "bullet_name": sort = b.Name case "bullet_manufacturer_name": sort = bm.Name case "primer_manufacturer_name": sort = pm.Name case "primer_name": sort = p.Name case "powder_manufacturer_name": sort = pwdm.Name case "powder_name": sort = pwd.Name case "powder_grs": sort = l.PowderGr default: sort = ctg.Name } if c.QueryParam("sortOrder") == "desc" { q.ORDER_BY(sort.DESC()) } else { q.ORDER_BY(sort.ASC()) } limit := c.QueryParam("limit") if limit == "" { limit = "50" } pageInt := handlers.ParseInt64OrDefault(c.QueryParam("page"), 1) if pageInt < 1 { pageInt = 1 } offset := (pageInt - 1) * handlers.ParseInt64OrDefault(limit, 50) q = q.LIMIT(handlers.ParseInt64OrDefault(limit, 50)). OFFSET(offset) return q } func getUuidExpr(ids []string) []postgres.Expression { expr := make([]postgres.Expression, 0) for _, id := range ids { uuid, err := handlers.ParseUuid(id) if err != nil { continue } expr = append(expr, postgres.UUID(uuid)) } return expr }