diff --git a/backend/src/controllers/receipts.c b/backend/src/controllers/receipts.c index 17c6414..28321ab 100644 --- a/backend/src/controllers/receipts.c +++ b/backend/src/controllers/receipts.c @@ -72,37 +72,49 @@ void route_get_receipts_one(HttpCtx* ctx) free(receipt_id_str); Receipt receipt; - DbRes db_rizz = db_receipt_with_id(cx->db, &receipt, receipt_id); - if (db_rizz != DbRes_Ok) { + DbRes db_res = db_receipt_with_id(cx->db, &receipt, receipt_id); + if (db_res != DbRes_Ok) { RESPOND_BAD_REQUEST(ctx, "receipt not found"); return; } ProductPriceVec product_prices = { 0 }; product_price_vec_construct(&product_prices); - db_receipt_prices(cx->db, &product_prices, receipt_id); - - String res; - string_construct(&res); - - char* receipt_str = receipt_to_json_string(&receipt); - - string_pushf( - &res, "{\"ok\":true,\"receipt\":%s,\"product_prices\":[", receipt_str); - - for (size_t i = 0; i < product_prices.size; ++i) { - if (i != 0) { - string_pushf(&res, ","); - } - char* product_price_str - = product_price_to_json_string(&product_prices.data[i]); - string_pushf(&res, "%s", product_price_str); - free(product_price_str); + db_res = db_receipt_prices(cx->db, &product_prices, receipt_id); + if (db_res != DbRes_Ok) { + RESPOND_SERVER_ERROR(ctx); + return; } - string_pushf(&res, "]}"); - RESPOND_JSON(ctx, 200, "%s", res.data); - string_destroy(&res); + ProductVec products = { 0 }; + product_vec_construct(&products); + db_res = db_receipt_products(cx->db, &products, receipt_id); + if (db_res != DbRes_Ok) { + RESPOND_SERVER_ERROR(ctx); + return; + } + + ReceiptsOneRes res = { + .receipt_id = receipt.id, + .timestamp = receipt.timestamp, + .products = (ReceiptsOneResProductVec) { 0 }, + }; + receipts_one_res_product_vec_construct(&res.products); + for (size_t i = 0; i < receipt.products.size; ++i) { + receipts_one_res_product_vec_push(&res.products, + (ReceiptsOneResProduct) { + .product_id = products.data[i].id, + .name = products.data[i].name, + .price_dkk_cent = product_prices.data[i].price_dkk_cent, + }); + } + + char* res_json = receipts_one_res_to_json_string(&res); + + RESPOND_JSON(ctx, 200, "{\"ok\":true,\"receipt\":%s}", res_json); + free(res_json); + receipts_one_res_destroy(&res); + product_vec_destroy(&products); product_price_vec_destroy(&product_prices); receipt_destroy(&receipt); } diff --git a/backend/src/db/db.h b/backend/src/db/db.h index 6032cd8..97efd59 100644 --- a/backend/src/db/db.h +++ b/backend/src/db/db.h @@ -30,6 +30,9 @@ DbRes db_user_with_email_exists(Db* db, bool* exists, const char* email); /// `user` is an out parameter. DbRes db_user_with_email(Db* db, User* user, const char* email); +/// `product` is an out parameter. +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); @@ -49,3 +52,7 @@ DbRes db_receipt_with_id(Db* db, Receipt* receipt, int64_t id); /// Expects `product_prices` to be constructed. DbRes db_receipt_prices( Db* db, ProductPriceVec* product_prices, int64_t receipt_id); + +/// `products` field is an out parameter. +/// Expects `products` to be constructed. +DbRes db_receipt_products(Db* db, ProductVec* products, int64_t receipt_id); diff --git a/backend/src/db/db_sqlite.c b/backend/src/db/db_sqlite.c index aa11bbc..260dce7 100644 --- a/backend/src/db/db_sqlite.c +++ b/backend/src/db/db_sqlite.c @@ -225,7 +225,7 @@ DbRes db_user_with_email_exists(Db* db, bool* exists, const char* email) *exists = true; } if (sqlite_res != SQLITE_DONE) { - fprintf(stderr, "error: %s\n", sqlite3_errmsg(connection)); + REPORT_SQLITE3_ERROR(); res = DbRes_Error; goto l0_return; } @@ -285,6 +285,55 @@ l0_return: return res; } +/// `product` is an out parameter. +DbRes db_product_with_id(Db* db, Product* product, int64_t id) +{ + static_assert(sizeof(Product) == 48, "model has changed"); + + sqlite3* connection; + CONNECT; + DbRes res; + + sqlite3_stmt* stmt; + int prepare_res = sqlite3_prepare_v2(connection, + "SELECT id, name, price_dkk_cent, description, coord, barcode FROM " + "products WHERE id = ?", + -1, + &stmt, + NULL); + if (prepare_res != SQLITE_OK) { + REPORT_SQLITE3_ERROR(); + res = DbRes_Error; + goto l0_return; + } + sqlite3_bind_int64(stmt, 1, 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; + } + *product = (Product){ + .id = GET_INT(0), + .name = GET_STR(1), + .price_dkk_cent = GET_INT(2), + .description = GET_STR(3), + .coord_id = GET_INT(4), + .barcode = GET_STR(5), + }, + + res = DbRes_Ok; +l0_return: + if (stmt) + sqlite3_finalize(stmt); + DISCONNECT; + return res; +} + DbRes db_product_all(Db* db, ProductVec* vec) { static_assert(sizeof(Product) == 48, "model has changed"); @@ -668,3 +717,63 @@ l0_return: DISCONNECT; return res; } + +DbRes db_receipt_products(Db* db, ProductVec* products, int64_t receipt_id) +{ + static_assert(sizeof(Product) == 48, "model has changed"); + + sqlite3* connection; + CONNECT; + DbRes res; + sqlite3_stmt* stmt = NULL; + + int prepare_res = sqlite3_prepare_v2(connection, + "SELECT" + " products.id," + " products.name," + " products.price_dkk_cent," + " products.description," + " products.coord," + " products.barcode" + " FROM receipt_products" + " JOIN product_prices" + " ON receipt_products.product_price = product_prices.id" + " JOIN products" + " ON product_prices.product = products.id" + " WHERE receipt_products.receipt = ?", + -1, + &stmt, + NULL); + + if (prepare_res != SQLITE_OK) { + REPORT_SQLITE3_ERROR(); + res = DbRes_Error; + goto l0_return; + } + sqlite3_bind_int64(stmt, 1, receipt_id); + + int sqlite_res; + while ((sqlite_res = sqlite3_step(stmt)) == SQLITE_ROW) { + Product product = { + .id = GET_INT(0), + .name = GET_STR(1), + .price_dkk_cent = GET_INT(2), + .description = GET_STR(3), + .coord_id = GET_INT(4), + .barcode = GET_STR(5), + }; + product_vec_push(products, product); + } + if (sqlite_res != SQLITE_DONE) { + fprintf(stderr, "error: %s\n", sqlite3_errmsg(connection)); + res = DbRes_Error; + goto l0_return; + } + + res = DbRes_Ok; +l0_return: + if (stmt) + sqlite3_finalize(stmt); + DISCONNECT; + return res; +} diff --git a/backend/src/models/models.c b/backend/src/models/models.c index 0f1de7c..0a00f7d 100644 --- a/backend/src/models/models.c +++ b/backend/src/models/models.c @@ -77,6 +77,21 @@ void carts_purchase_req_destroy(CartsPurchaseReq* model) carts_item_vec_destroy(&model->items); } +void receipts_one_res_product_destroy(ReceiptsOneResProduct* m) +{ + static_assert(sizeof(ReceiptsOneResProduct) == 32, "model has changed"); + + free(m->name); +} + +void receipts_one_res_destroy(ReceiptsOneRes* m) +{ + static_assert(sizeof(ReceiptsOneRes) == 40, "model has changed"); + + free(m->timestamp); + receipts_one_res_product_vec_destroy(&m->products); +} + char* user_to_json_string(const User* m) { static_assert(sizeof(User) == 40, "model has changed"); @@ -208,7 +223,7 @@ char* receipt_to_json_string(const Receipt* m) return result; } -char* users_register_req_to_json(const UsersRegisterReq* m) +char* users_register_req_to_json_string(const UsersRegisterReq* m) { static_assert(sizeof(UsersRegisterReq) == 24, "model has changed"); @@ -229,7 +244,7 @@ char* users_register_req_to_json(const UsersRegisterReq* m) return result; } -char* sessions_login_req_to_json(const SessionsLoginReq* m) +char* sessions_login_req_to_json_string(const SessionsLoginReq* m) { static_assert(sizeof(SessionsLoginReq) == 16, "model has changed"); @@ -248,13 +263,66 @@ char* sessions_login_req_to_json(const SessionsLoginReq* m) return result; } -char* carts_purchase_req_to_json(const CartsPurchaseReq* m) +char* carts_purchase_req_to_json_string(const CartsPurchaseReq* m) { static_assert(sizeof(CartsPurchaseReq) == 24, "model has changed"); PANIC("not implemented"); } +char* receipts_one_res_product_to_json_string(const ReceiptsOneResProduct* m) +{ + static_assert(sizeof(ReceiptsOneResProduct) == 32, "model has changed"); + + String string; + string_construct(&string); + string_pushf(&string, + "{" + "\"product_id\":\"%ld\"," + "\"name\":\"%s\"," + "\"price_dkk_cent\":\"%ld\"," + "\"price_dkk_cent\":\"%ld\"" + "}", + m->product_id, + m->name, + m->price_dkk_cent, + m->amount); + + char* result = string_copy(&string); + string_destroy(&string); + return result; +} + +char* receipts_one_res_to_json_string(const ReceiptsOneRes* m) +{ + static_assert(sizeof(ReceiptsOneRes) == 40, "model has changed"); + + String string; + string_construct(&string); + string_pushf(&string, + "{" + "\"receipt_id\":\"%ld\"," + "\"timestamp\":\"%s\"," + "\"products:\":[", + m->receipt_id, + m->timestamp); + + for (size_t i = 0; i < m->products.size; ++i) { + if (i != 0) { + string_pushf(&string, ","); + } + char* product + = receipts_one_res_product_to_json_string(&m->products.data[i]); + string_push_str(&string, product); + } + + string_pushf(&string, "]}"); + + char* result = string_copy(&string); + string_destroy(&string); + return result; +} + typedef struct { const char* key; JsonType type; @@ -454,6 +522,24 @@ int carts_purchase_req_from_json(CartsPurchaseReq* m, const JsonValue* json) return 0; } +int receipts_one_res_product_from_json( + ReceiptsOneResProduct* m, const JsonValue* json) +{ + static_assert(sizeof(ReceiptsOneResProduct) == 32, "model has changed"); + + PANIC("not implemented"); +} + +int receipts_one_res_from_json(ReceiptsOneRes* m, const JsonValue* json) +{ + static_assert(sizeof(ReceiptsOneRes) == 40, "model has changed"); + + PANIC("not implemented"); +} + DEFINE_VEC_IMPL(ProductPrice, ProductPriceVec, product_price_vec, ) DEFINE_VEC_IMPL(ReceiptProduct, ReceiptProductVec, receipt_product_vec, ) DEFINE_VEC_IMPL(CartsItem, CartsItemVec, carts_item_vec, ) +DEFINE_VEC_IMPL(ReceiptsOneResProduct, + ReceiptsOneResProductVec, + receipts_one_res_product_vec, ) diff --git a/backend/src/models/models.h b/backend/src/models/models.h index 02b3ea7..6ecf7d5 100644 --- a/backend/src/models/models.h +++ b/backend/src/models/models.h @@ -86,3 +86,24 @@ typedef struct { } CartsPurchaseReq; void carts_purchase_req_destroy(CartsPurchaseReq* model); + +typedef struct { + int64_t product_id; + char* name; + int64_t price_dkk_cent; + int64_t amount; +} ReceiptsOneResProduct; + +void receipts_one_res_product_destroy(ReceiptsOneResProduct* model); + +DECLARE_VEC_TYPE(ReceiptsOneResProduct, + ReceiptsOneResProductVec, + receipts_one_res_product_vec, ) + +typedef struct { + int64_t receipt_id; + char* timestamp; + ReceiptsOneResProductVec products; +} ReceiptsOneRes; + +void receipts_one_res_destroy(ReceiptsOneRes* model); diff --git a/backend/src/models/models_json.h b/backend/src/models/models_json.h index 6616e37..d64750a 100644 --- a/backend/src/models/models_json.h +++ b/backend/src/models/models_json.h @@ -14,3 +14,5 @@ DEFINE_MODEL_JSON(Receipt, receipt) DEFINE_MODEL_JSON(UsersRegisterReq, users_register_req) DEFINE_MODEL_JSON(SessionsLoginReq, sessions_login_req) DEFINE_MODEL_JSON(CartsPurchaseReq, carts_purchase_req) +DEFINE_MODEL_JSON(ReceiptsOneResProduct, receipts_one_res_product) +DEFINE_MODEL_JSON(ReceiptsOneRes, receipts_one_res) diff --git a/backend/test/test.ts b/backend/test/test.ts index 14edc24..45092d7 100644 --- a/backend/test/test.ts +++ b/backend/test/test.ts @@ -45,8 +45,8 @@ Deno.test("test backend", async (t) => { { "Session-Token": token! }, ); - assertEquals(sessionUserRes.ok, true); // console.log(sessionUserRes.user); + assertEquals(sessionUserRes.ok, true); }); await t.step("test /api/users/balance/add", async () => {