diff --git a/backend/src/controllers/controllers.h b/backend/src/controllers/controllers.h index 9f89a93..0b592b8 100644 --- a/backend/src/controllers/controllers.h +++ b/backend/src/controllers/controllers.h @@ -38,6 +38,8 @@ void route_get_not_found(HttpCtx* ctx); void route_get_products_all(HttpCtx* ctx); void route_post_products_create(HttpCtx* ctx); void route_post_products_update(HttpCtx* ctx); +void route_get_products_coords(HttpCtx* ctx); +void route_post_products_set_coords(HttpCtx* ctx); void route_post_products_set_image(HttpCtx* ctx); void route_get_products_image_png(HttpCtx* ctx); diff --git a/backend/src/controllers/products.c b/backend/src/controllers/products.c index 085aafa..ce87a11 100644 --- a/backend/src/controllers/products.c +++ b/backend/src/controllers/products.c @@ -2,6 +2,7 @@ #include "../models/models_json.h" #include "../utils/str.h" #include "controllers.h" +#include #include void route_get_products_all(HttpCtx* ctx) @@ -82,10 +83,8 @@ void route_post_products_update(HttpCtx* ctx) Cx* cx = http_ctx_user_ctx(ctx); const char* body_str = http_ctx_req_body_str(ctx); - printf("body_str = '%s'\n", body_str); JsonValue* body_json = json_parse(body_str, strlen(body_str)); - printf("body_json = %p\n", (void*)body_json); if (!body_json) { RESPOND_BAD_REQUEST(ctx, "bad request"); @@ -94,7 +93,6 @@ void route_post_products_update(HttpCtx* ctx) Product product; int parse_result = product_from_json(&product, body_json); - printf("parse_result = %d\n", parse_result); json_free(body_json); if (parse_result != 0) { RESPOND_BAD_REQUEST(ctx, "bad request"); @@ -113,6 +111,102 @@ l0_return: product_destroy(&product); } +void route_get_products_coords(HttpCtx* ctx) +{ + Cx* cx = http_ctx_user_ctx(ctx); + + const char* query = http_ctx_req_query(ctx); + if (!query) { + RESPOND_BAD_REQUEST(ctx, "no product_id parameter"); + return; + } + HttpQueryParams* params = http_parse_query_params(query); + char* product_id_str = http_query_params_get(params, "product_id"); + http_query_params_free(params); + if (!product_id_str) { + RESPOND_BAD_REQUEST(ctx, "no product_id parameter"); + return; + } + + int64_t product_id = strtol(product_id_str, NULL, 10); + free(product_id_str); + + Coord coord; + DbRes db_res = db_coord_with_product_id(cx->db, &coord, product_id); + if (db_res == DbRes_NotFound) { + RESPOND_JSON(ctx, 200, "{\"ok\":true,\"found\":false}"); + return; + } else if (db_res != DbRes_Ok) { + RESPOND_SERVER_ERROR(ctx); + return; + } + + char* coord_json = coord_to_json_string(&coord); + + RESPOND_JSON( + ctx, 200, "{\"ok\":true,\"found\":true,\"coords\":%s}", coord_json); + free(coord_json); + coord_destroy(&coord); +} + +void route_post_products_set_coords(HttpCtx* ctx) +{ + Cx* cx = http_ctx_user_ctx(ctx); + + const char* body_str = http_ctx_req_body_str(ctx); + JsonValue* body_json = json_parse(body_str, strlen(body_str)); + if (!body_json) { + RESPOND_BAD_REQUEST(ctx, "bad request"); + return; + } + + ProductsCoordsSetReq req; + int parse_result = products_coords_set_req_from_json(&req, body_json); + json_free(body_json); + if (parse_result != 0) { + RESPOND_BAD_REQUEST(ctx, "bad request"); + return; + } + + Product product; + DbRes db_res = db_product_with_id(cx->db, &product, req.product_id); + if (db_res == DbRes_NotFound) { + RESPOND_BAD_REQUEST(ctx, "not found"); + goto l0_return; + } else if (db_res != DbRes_Ok) { + RESPOND_SERVER_ERROR(ctx); + goto l0_return; + } + + Coord coord = { + .id = 0, + .x = req.x, + .y = req.y, + }; + + int64_t coord_id; + db_res = db_coord_insert(cx->db, &coord, &coord_id); + if (db_res != DbRes_Ok) { + RESPOND_SERVER_ERROR(ctx); + goto l1_return; + } + + product.coord_id = coord_id; + + db_res = db_product_update(cx->db, &product); + if (db_res != DbRes_Ok) { + RESPOND_SERVER_ERROR(ctx); + goto l1_return; + } + + RESPOND_JSON(ctx, 200, "{\"ok\":true}"); +l1_return: + coord_destroy(&coord); + products_coords_set_req_destroy(&req); +l0_return: + product_destroy(&product); +} + void route_post_products_set_image(HttpCtx* ctx) { Cx* cx = http_ctx_user_ctx(ctx); diff --git a/backend/src/db/db.h b/backend/src/db/db.h index b743ed8..9079301 100644 --- a/backend/src/db/db.h +++ b/backend/src/db/db.h @@ -25,6 +25,7 @@ DbRes db_user_update(Db* db, const User* user); /// `user` field is an out parameter. DbRes db_user_with_id(Db* db, User* user, int64_t id); +/// `exists` field is an out parameter. DbRes db_user_with_email_exists(Db* db, bool* exists, const char* email); /// `user` is an out parameter. @@ -42,6 +43,12 @@ DbRes db_product_with_id(Db* db, Product* product, int64_t id); /// Expects `vec` to be constructed. DbRes db_product_all(Db* db, ProductVec* vec); +/// `coord.id` are ignored. +DbRes db_coord_insert(Db* db, const Coord* coord, int64_t* id); + +/// `coord` is an out parameter. +DbRes db_coord_with_product_id(Db* db, Coord* coord, int64_t product_id); + /// `product_price` is an out parameter. DbRes db_product_price_of_product( Db* db, ProductPrice* product_price, int64_t product_id); diff --git a/backend/src/db/db_sqlite.c b/backend/src/db/db_sqlite.c index bc5b4a7..5440617 100644 --- a/backend/src/db/db_sqlite.c +++ b/backend/src/db/db_sqlite.c @@ -472,6 +472,100 @@ l0_return: return res; } +/// `coord.id` are ignored. +DbRes db_coord_insert(Db* db, const Coord* coord, int64_t* id) +{ + static_assert(sizeof(Coord) == 24, "model has changed"); + + sqlite3* connection; + CONNECT; + DbRes res; + + sqlite3_stmt* stmt; + int prepare_res = sqlite3_prepare_v2(connection, + "INSERT INTO coords" + " (x, y)" + " VALUES (?, ?)", + -1, + &stmt, + NULL); + if (prepare_res != SQLITE_OK) { + REPORT_SQLITE3_ERROR(); + res = DbRes_Error; + goto l0_return; + } + + sqlite3_bind_int64(stmt, 1, coord->x); + sqlite3_bind_int64(stmt, 2, coord->y); + + int step_res = sqlite3_step(stmt); + if (step_res != SQLITE_DONE) { + REPORT_SQLITE3_ERROR(); + res = DbRes_Error; + goto l0_return; + } + + int64_t coord_id = sqlite3_last_insert_rowid(connection); + + if (id) { + *id = coord_id; + } + + res = DbRes_Ok; +l0_return: + if (stmt) + sqlite3_finalize(stmt); + DISCONNECT; + return res; +} + +DbRes db_coord_with_product_id(Db* db, Coord* coord, int64_t product_id) +{ + static_assert(sizeof(Coord) == 24, "model has changed"); + + sqlite3* connection; + CONNECT; + DbRes res; + + sqlite3_stmt* stmt; + int prepare_res = sqlite3_prepare_v2(connection, + "SELECT coords.id, coords.x, coords.y" + " FROM coords" + " JOIN products ON products.coord = coords.id" + " WHERE products.id = ?", + -1, + &stmt, + NULL); + if (prepare_res != SQLITE_OK) { + REPORT_SQLITE3_ERROR(); + res = DbRes_Error; + goto l0_return; + } + sqlite3_bind_int64(stmt, 1, product_id); + + int step_res = sqlite3_step(stmt); + if (step_res == SQLITE_DONE) { + res = DbRes_NotFound; + goto l0_return; + } else if (step_res != SQLITE_ROW) { + REPORT_SQLITE3_ERROR(); + res = DbRes_Error; + goto l0_return; + } + *coord = (Coord){ + .id = GET_INT(0), + .x = GET_INT(1), + .y = GET_INT(2), + }, + + res = DbRes_Ok; +l0_return: + if (stmt) + sqlite3_finalize(stmt); + DISCONNECT; + return res; +} + static inline DbRes get_product_price_from_product_id( sqlite3* connection, int64_t product_id, int64_t* price) { diff --git a/backend/src/main.c b/backend/src/main.c index 1b70c13..11c942a 100644 --- a/backend/src/main.c +++ b/backend/src/main.c @@ -40,6 +40,9 @@ int main(void) server, "/api/products/create", route_post_products_create); http_server_post( server, "/api/products/update", route_post_products_update); + http_server_get(server, "/api/products/coords", route_get_products_coords); + http_server_post( + server, "/api/products/set_coords", route_post_products_set_coords); http_server_post( server, "/api/products/set-image", route_post_products_set_image); http_server_get( diff --git a/backend/src/models/models.c b/backend/src/models/models.c index 7c16a98..d6239cd 100644 --- a/backend/src/models/models.c +++ b/backend/src/models/models.c @@ -113,6 +113,13 @@ void products_create_req_destroy(ProductsCreateReq* m) free(m->barcode); } +void products_coords_set_req_destroy(ProductsCoordsSetReq* model) +{ + static_assert(sizeof(ProductsCoordsSetReq) == 24, "model has changed"); + + (void)model; +} + char* user_to_json_string(const User* m) { static_assert(sizeof(User) == 40, "model has changed"); @@ -397,6 +404,27 @@ char* products_create_req_to_json_string(const ProductsCreateReq* m) return result; } +char* products_coords_set_req_to_json_string(const ProductsCoordsSetReq* m) +{ + static_assert(sizeof(ProductsCoordsSetReq) == 24, "model has changed"); + + String string; + string_construct(&string); + string_pushf(&string, + "{" + "\"product_id\":%ld," + "\"x\":%ld," + "\"y\":%ld" + "}", + m->product_id, + m->x, + m->y); + + char* result = string_copy(&string); + string_destroy(&string); + return result; +} + typedef struct { const char* key; JsonType type; @@ -639,6 +667,25 @@ int products_create_req_from_json(ProductsCreateReq* m, const JsonValue* json) }; return 0; } +int products_coords_set_req_from_json( + ProductsCoordsSetReq* m, const JsonValue* json) +{ + static_assert(sizeof(ProductsCoordsSetReq) == 24, "model has changed"); + + ObjField fields[] = { + { "product_id", JsonType_Number }, + { "x", JsonType_Number }, + { "y", JsonType_Number }, + }; + if (!OBJ_CONFORMS(json, fields)) + return -1; + *m = (ProductsCoordsSetReq) { + .product_id = GET_INT("product_id"), + .x = GET_INT("x"), + .y = GET_INT("y"), + }; + return 0; +} DEFINE_VEC_IMPL(ProductPrice, ProductPriceVec, product_price_vec, ) DEFINE_VEC_IMPL(ReceiptProduct, ReceiptProductVec, receipt_product_vec, ) diff --git a/backend/src/models/models.h b/backend/src/models/models.h index 7288b6b..205a945 100644 --- a/backend/src/models/models.h +++ b/backend/src/models/models.h @@ -131,3 +131,11 @@ typedef struct { } ProductsCreateReq; void products_create_req_destroy(ProductsCreateReq* model); + +typedef struct { + int64_t product_id; + int64_t x; + int64_t y; +} ProductsCoordsSetReq; + +void products_coords_set_req_destroy(ProductsCoordsSetReq* model); diff --git a/backend/src/models/models_json.h b/backend/src/models/models_json.h index ba8b530..fa223f4 100644 --- a/backend/src/models/models_json.h +++ b/backend/src/models/models_json.h @@ -18,3 +18,4 @@ DEFINE_MODEL_JSON(CartsPurchaseReq, carts_purchase_req) DEFINE_MODEL_JSON(ReceiptsOneResProduct, receipts_one_res_product) DEFINE_MODEL_JSON(ReceiptsOneRes, receipts_one_res) DEFINE_MODEL_JSON(ProductsCreateReq, products_create_req) +DEFINE_MODEL_JSON(ProductsCoordsSetReq, products_coords_set_req) diff --git a/backend/test/test.ts b/backend/test/test.ts index 8945894..a939eae 100644 --- a/backend/test/test.ts +++ b/backend/test/test.ts @@ -67,6 +67,8 @@ Deno.test("test backend", async (t) => { await testCartsAndReceipts(t, token!); + await testProductsCoords(t); + await t.step("test /api/sessions/logout", async () => { const logoutRes = await post<{ ok: boolean }>( "/api/sessions/logout", @@ -154,6 +156,72 @@ async function testCartsAndReceipts(t: Deno.TestContext, token: string) { }); } +async function testProductsCoords(t: Deno.TestContext) { + const productId = 1; + let coords: { x: number; y: number } | null = null; + + await t.step("test /api/products/coords", async () => { + const res = await get<{ + ok: boolean; + found: boolean; + coords: { x: number; y: number }; + }>( + `/api/products/coords?product_id=${productId}`, + {}, + ); + + // console.log(res); + assertEquals(res.ok, true); + if (res.found) { + coords = res.coords; + } else { + coords = null; + } + }); + + await t.step("test /api/products/set_coords", async () => { + const res = await post<{ ok: boolean }>( + "/api/products/set_coords", + { + product_id: productId, + x: 1, + y: 2, + }, + {}, + ); + + // console.log(res); + assertEquals(res.ok, true); + }); + + await t.step("test /api/products/coords", async () => { + const res = await get<{ + ok: boolean; + found: true; + coords: { x: number; y: number }; + }>( + `/api/products/coords?product_id=${productId}`, + {}, + ); + + // console.log(res); + assertEquals(res.ok, true); + assertEquals(res.found, true); + assertEquals(res.coords.x, 1); + assertEquals(res.coords.y, 2); + }); + + if (coords !== null) { + const res = await post<{ ok: boolean }>( + "/api/products/set_coords", + { product_id: productId, ...coords as { x: number; y: number } }, + {}, + ); + // console.log(res); + assertEquals(res.ok, true); + } +} + type SessionUser = { id: number; email: string; balance_dkk_cent: number }; async function sessionUser(