implement api/cart

This commit is contained in:
Mikkel Troels Kongsted 2025-03-13 14:48:52 +01:00
parent 70b8244e62
commit b486bb7970
9 changed files with 133 additions and 12 deletions

View File

@ -42,9 +42,12 @@ CREATE TABLE IF NOT EXISTS carts (
CREATE TABLE IF NOT EXISTS cart_items ( CREATE TABLE IF NOT EXISTS cart_items (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
cart INTEGER NOT NULL, cart INTEGER NOT NULL,
product INTEGER NOT NULL,
amount INTEGER NOT NULL, amount INTEGER NOT NULL,
FOREIGN KEY(cart) REFERENCES carts(id)
FOREIGN KEY(cart) REFERENCES carts(id),
FOREIGN KEY(product) REFERENCES product(id)
); );

View File

@ -32,6 +32,8 @@ void route_get_not_found(HttpCtx* ctx);
void route_get_products_all(HttpCtx* ctx); void route_get_products_all(HttpCtx* ctx);
void route_get_cart_items_from_session(HttpCtx* ctx);
void route_post_users_register(HttpCtx* ctx); void route_post_users_register(HttpCtx* ctx);
void route_post_sessions_login(HttpCtx* ctx); void route_post_sessions_login(HttpCtx* ctx);

View File

@ -0,0 +1,42 @@
#include "../controllers.h"
#include "../http_server.h"
#include "../models_json.h"
#include "../str_util.h"
void route_get_cart_items_from_session(HttpCtx* ctx)
{
Cx* cx = http_ctx_user_ctx(ctx);
const Session* session = middleware_session(ctx);
if (!session)
return;
CartItemVec cart_items;
cart_item_vec_construct(&cart_items);
DbRes db_res = db_cart_items_with_user_id(cx->db, &cart_items, session->user_id);
if (db_res != DbRes_Ok) {
RESPOND_BAD_REQUEST(ctx, "Could not find cart for user");
return;
}
String res;
string_construct(&res);
string_push_str(&res, "{\"ok\":true,\"products\":[");
for (size_t i = 0; i < cart_items.size; ++i) {
if (i != 0) {
string_push_str(&res, ",");
}
char* json = cart_item_to_json_string(&cart_items.data[i]);
string_push_str(&res, json);
free(json);
}
string_push_str(&res, "]}");
cart_item_vec_destroy(&cart_items);
RESPOND_JSON(ctx, 200, "%s", res.data);
string_destroy(&res);
}

View File

@ -27,7 +27,7 @@ void route_post_sessions_login(HttpCtx* ctx)
} }
User user; User user;
DbRes db_res = db_user_from_email(cx->db, &user, req.email); DbRes db_res = db_user_with_email(cx->db, &user, req.email);
if (db_res == DbRes_NotFound) { if (db_res == DbRes_NotFound) {
RESPOND_BAD_REQUEST(ctx, "user with email not found"); RESPOND_BAD_REQUEST(ctx, "user with email not found");
goto l0_return; goto l0_return;
@ -71,7 +71,7 @@ void route_get_sessions_user(HttpCtx* ctx)
return; return;
User user; User user;
DbRes db_res = db_user_from_id(cx->db, &user, session->user_id); DbRes db_res = db_user_with_id(cx->db, &user, session->user_id);
if (db_res != DbRes_Ok) { if (db_res != DbRes_Ok) {
RESPOND_BAD_REQUEST(ctx, "user not found"); RESPOND_BAD_REQUEST(ctx, "user not found");
return; return;

View File

@ -2,9 +2,12 @@
#include "collection.h" #include "collection.h"
#include "models.h" #include "models.h"
#include <stdint.h>
DEFINE_VEC(int64_t, Ids, ids, 8) DEFINE_VEC(int64_t, Ids, ids, 8)
DEFINE_VEC(Product, ProductVec, product_vec, 32) DEFINE_VEC(Product, ProductVec, product_vec, 32)
DEFINE_VEC(CartItem, CartItemVec, cart_item_vec, 32)
typedef enum { typedef enum {
DbRes_Ok, DbRes_Ok,
@ -17,7 +20,7 @@ typedef struct Db Db;
/// `user.id` field is ignored. /// `user.id` field is ignored.
DbRes db_user_insert(Db* db, const User* user); DbRes db_user_insert(Db* db, const User* user);
/// `user` field is an out parameter. /// `user` field is an out parameter.
DbRes db_user_from_id(Db* db, User* user, int64_t id); DbRes db_user_with_id(Db* db, User* user, int64_t id);
/// Expects `ids` to be constructed. /// Expects `ids` to be constructed.
@ -25,7 +28,10 @@ DbRes db_users_with_email(Db* db, Ids* ids, const char* email);
/// `user` is an out parameter. /// `user` is an out parameter.
DbRes db_user_from_email(Db* db, User* user, const char* email); DbRes db_user_with_email(Db* db, User* user, const char* email);
/// Expects `vec` to be constructed. /// Expects `vec` to be constructed.
DbRes db_product_all(Db* db, ProductVec* vec); DbRes db_product_all(Db* db, ProductVec* vec);
DbRes db_cart_items_with_user_id(Db* db, CartItemVec* vec, int64_t user_id);

View File

@ -4,6 +4,7 @@
#include "str_util.h" #include "str_util.h"
#include <assert.h> #include <assert.h>
#include <sqlite3.h> #include <sqlite3.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
static inline char* get_str_safe(sqlite3_stmt* stmt, int col) static inline char* get_str_safe(sqlite3_stmt* stmt, int col)
@ -100,7 +101,7 @@ l0_return:
return res; return res;
} }
DbRes db_user_from_id(Db* db, User* user, int64_t id) DbRes db_user_with_id(Db* db, User* user, int64_t id)
{ {
static_assert(sizeof(User) == 40, "model has changed"); static_assert(sizeof(User) == 40, "model has changed");
@ -118,7 +119,6 @@ DbRes db_user_from_id(Db* db, User* user, int64_t id)
int step_res = sqlite3_step(stmt); int step_res = sqlite3_step(stmt);
if (step_res == SQLITE_DONE) { if (step_res == SQLITE_DONE) {
res = DbRes_NotFound; res = DbRes_NotFound;
puts("didn't find user");
goto l0_return; goto l0_return;
} else if (step_res != SQLITE_ROW) { } else if (step_res != SQLITE_ROW) {
fprintf(stderr, "error: %s\n", sqlite3_errmsg(connection)); fprintf(stderr, "error: %s\n", sqlite3_errmsg(connection));
@ -173,7 +173,7 @@ l0_return:
return res; return res;
} }
DbRes db_user_from_email(Db* db, User* user, const char* email) DbRes db_user_with_email(Db* db, User* user, const char* email)
{ {
static_assert(sizeof(User) == 40, "model has changed"); static_assert(sizeof(User) == 40, "model has changed");
@ -263,3 +263,65 @@ l0_return:
DISCONNECT; DISCONNECT;
return res; return res;
} }
DbRes db_cart_items_with_user_id(Db* db, CartItemVec* vec, int64_t user_id) {
static_assert(sizeof(Cart) == 16, "model has changed");
static_assert(sizeof(CartItem) == 32, "model has changed");
sqlite3* connection;
CONNECT;
DbRes res;
sqlite3_stmt* stmt;
int sqlite_res = sqlite3_prepare_v2(connection,
"SELECT id "
" FROM carts WHERE user = ?",
-1, &stmt, NULL);
if (sqlite_res != SQLITE_OK) {
fprintf(stderr, "error: %s\n at %s:%d\n", sqlite3_errmsg(connection),
__func__, __LINE__);
res = DbRes_Error;
goto l0_return;
}
sqlite3_bind_int64(stmt, 1, user_id);
int step_res = sqlite3_step(stmt);
if (step_res == SQLITE_DONE) {
res = DbRes_NotFound;
goto l0_return;
} else if (step_res != SQLITE_ROW) {
fprintf(stderr, "error: %s\n", sqlite3_errmsg(connection));
res = DbRes_Error;
goto l0_return;
}
int64_t cart_id = GET_INT(0);
sqlite_res = sqlite3_prepare_v2(
connection, "SELECT id, cart, product, amount FROM cart_items WHERE cart = ?", -1, &stmt, NULL);
sqlite3_bind_int64(stmt, 1, cart_id);
while ((sqlite_res = sqlite3_step(stmt)) == SQLITE_ROW) {
CartItem cart_item = {
.id = GET_INT(0),
.cart_id = GET_INT(1),
.product_id = GET_INT(2),
.amount = GET_INT(3),
};
cart_item_vec_push(vec, cart_item);
}
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;
}

View File

@ -43,6 +43,8 @@ int main(void)
http_server_get(server, "/api/products/all", route_get_products_all); http_server_get(server, "/api/products/all", route_get_products_all);
http_server_get(server, "/api/cart", route_get_cart_items_from_session);
http_server_post(server, "/api/users/register", route_post_users_register); http_server_post(server, "/api/users/register", route_post_users_register);
http_server_post(server, "/api/sessions/login", route_post_sessions_login); http_server_post(server, "/api/sessions/login", route_post_sessions_login);
http_server_post( http_server_post(

View File

@ -47,7 +47,7 @@ void cart_destroy(Cart* m)
void cart_item_destroy(CartItem* m) void cart_item_destroy(CartItem* m)
{ {
static_assert(sizeof(CartItem) == 24, "model has changed"); static_assert(sizeof(CartItem) == 32, "model has changed");
(void)m; (void)m;
} }
@ -170,7 +170,7 @@ char* cart_to_json_string(const Cart* m)
char* cart_item_to_json_string(const CartItem* m) char* cart_item_to_json_string(const CartItem* m)
{ {
static_assert(sizeof(CartItem) == 24, "model has changed"); static_assert(sizeof(CartItem) == 32, "model has changed");
String string; String string;
string_construct(&string); string_construct(&string);
@ -178,9 +178,10 @@ char* cart_item_to_json_string(const CartItem* m)
"{" "{"
"\"id\":%ld," "\"id\":%ld,"
"\"cart_id\":%ld," "\"cart_id\":%ld,"
"\"product_id\":%ld,"
"\"amount\":%ld" "\"amount\":%ld"
"}", "}",
m->id, m->cart_id, m->amount); m->id, m->cart_id, m->product_id, m->amount);
char* result = string_copy(&string); char* result = string_copy(&string);
string_destroy(&string); string_destroy(&string);
@ -354,11 +355,12 @@ int cart_from_json(Cart* m, const JsonValue* json)
int cart_item_from_json(CartItem* m, const JsonValue* json) int cart_item_from_json(CartItem* m, const JsonValue* json)
{ {
static_assert(sizeof(CartItem) == 24, "model has changed"); static_assert(sizeof(CartItem) == 32, "model has changed");
ObjField fields[] = { ObjField fields[] = {
{ "id", JsonType_Number }, { "id", JsonType_Number },
{ "cart_id", JsonType_Number }, { "cart_id", JsonType_Number },
{ "product_id", JsonType_Number },
{ "amount", JsonType_Number }, { "amount", JsonType_Number },
}; };
if (!obj_conforms(json, fields, sizeof(fields) / sizeof(fields[0]))) if (!obj_conforms(json, fields, sizeof(fields) / sizeof(fields[0])))
@ -366,6 +368,7 @@ int cart_item_from_json(CartItem* m, const JsonValue* json)
*m = (CartItem) { *m = (CartItem) {
.id = GET_INT("id"), .id = GET_INT("id"),
.cart_id = GET_INT("cart_id"), .cart_id = GET_INT("cart_id"),
.product_id = GET_INT("product_id"),
.amount = GET_INT("amount"), .amount = GET_INT("amount"),
}; };
return 0; return 0;

View File

@ -39,6 +39,7 @@ typedef struct {
typedef struct { typedef struct {
int64_t id; int64_t id;
int64_t cart_id; int64_t cart_id;
int64_t product_id;
int64_t amount; int64_t amount;
} CartItem; } CartItem;