mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-04-27 16:24:07 +02:00
add carts purchase
This commit is contained in:
parent
de4e91db72
commit
c49f0c05d2
@ -2,7 +2,7 @@
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
balance_dkk_cent INTEGER NOT NULL
|
||||
);
|
||||
@ -32,6 +32,24 @@ CREATE TABLE IF NOT EXISTS product_prices (
|
||||
FOREIGN KEY(product) REFERENCES products(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS receipts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user INTEGER NOT NULL,
|
||||
datetime INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY(user) REFERENCES users(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS receipt_products (
|
||||
id INTEGER PRIMARY KEY,
|
||||
receipt INTEGER NOT NULL,
|
||||
product_price INTEGER NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY(receipt) REFERENCES receipts(id)
|
||||
FOREIGN KEY(product_price) REFERENCES product_prices(id)
|
||||
);
|
||||
|
||||
INSERT OR REPLACE INTO users (name, email, password_hash, balance_dkk_cent)
|
||||
VALUES ('User','test@email.com','08ce0220f6d63d85c3ac313e308f4fca35ecfb850baa8ddb924cfab98137b6b18b4a8e027067cb98802757df1337246a0f3aa25c44c2b788517a871086419dcf',10000);
|
||||
|
||||
|
@ -25,12 +25,50 @@ void route_post_carts_purchase(HttpCtx* ctx)
|
||||
return;
|
||||
}
|
||||
|
||||
printf("product_id\tamount\n");
|
||||
for (size_t i = 0; i < req.items.size; ++i) {
|
||||
printf("%ld\t\t%ld\n",
|
||||
req.items.data[i].product_id,
|
||||
req.items.data[i].amount);
|
||||
size_t item_amount = req.items.size;
|
||||
|
||||
ProductPriceVec prices;
|
||||
product_price_vec_construct(&prices);
|
||||
|
||||
for (size_t i = 0; i < item_amount; ++i) {
|
||||
ProductPrice price;
|
||||
DbRes db_res = db_product_price_of_product(
|
||||
cx->db, &price, req.items.data[i].product_id);
|
||||
if (db_res != DbRes_Ok) {
|
||||
RESPOND_SERVER_ERROR(ctx);
|
||||
goto l0_return;
|
||||
}
|
||||
product_price_vec_push(&prices, price);
|
||||
}
|
||||
|
||||
RESPOND_JSON(ctx, 200, "{\"ok\":true}");
|
||||
Receipt receipt = {
|
||||
.id = 0,
|
||||
.user_id = session->user_id,
|
||||
.timestamp = NULL,
|
||||
.products = (ReceiptProductVec) { 0 },
|
||||
};
|
||||
receipt_product_vec_construct(&receipt.products);
|
||||
|
||||
for (size_t i = 0; i < item_amount; ++i) {
|
||||
receipt_product_vec_push(&receipt.products,
|
||||
(ReceiptProduct) {
|
||||
.id = 0,
|
||||
.receipt_id = 0,
|
||||
.product_price_id = prices.data[i].id,
|
||||
.amount = req.items.data[i].amount,
|
||||
});
|
||||
}
|
||||
|
||||
int64_t receipt_id;
|
||||
DbRes db_res = db_receipt_insert(cx->db, &receipt, &receipt_id);
|
||||
if (db_res != DbRes_Ok) {
|
||||
RESPOND_SERVER_ERROR(ctx);
|
||||
goto l0_return;
|
||||
}
|
||||
|
||||
RESPOND_JSON(ctx, 200, "{\"ok\":true,\"receipt_id\":%ld}", receipt_id);
|
||||
|
||||
l0_return:
|
||||
receipt_destroy(&receipt);
|
||||
product_price_vec_destroy(&prices);
|
||||
}
|
||||
|
@ -27,21 +27,17 @@ void route_post_users_register(HttpCtx* ctx)
|
||||
return;
|
||||
}
|
||||
|
||||
Ids ids;
|
||||
ids_construct(&ids);
|
||||
if (db_users_with_email(cx->db, &ids, req.email) != DbRes_Ok) {
|
||||
bool email_used;
|
||||
if (db_user_with_email_exists(cx->db, &email_used, req.email) != DbRes_Ok) {
|
||||
RESPOND_SERVER_ERROR(ctx);
|
||||
ids_destroy(&ids);
|
||||
users_register_req_destroy(&req);
|
||||
return;
|
||||
}
|
||||
if (ids.size > 0) {
|
||||
if (email_used) {
|
||||
RESPOND_BAD_REQUEST(ctx, "email in use");
|
||||
ids_destroy(&ids);
|
||||
users_register_req_destroy(&req);
|
||||
return;
|
||||
}
|
||||
ids_destroy(&ids);
|
||||
|
||||
char* password_hash = str_hash(req.password);
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include "../collections/collection.h"
|
||||
#include "../models/models.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
DEFINE_VEC(int64_t, Ids, ids)
|
||||
@ -20,11 +21,19 @@ DbRes db_user_insert(Db* db, const User* user);
|
||||
/// `user` field is an out parameter.
|
||||
DbRes db_user_with_id(Db* db, User* user, int64_t id);
|
||||
|
||||
/// Expects `ids` to be constructed.
|
||||
DbRes db_users_with_email(Db* db, Ids* ids, const char* email);
|
||||
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);
|
||||
|
||||
/// Expects `vec` to be constructed.
|
||||
DbRes db_product_all(Db* db, ProductVec* vec);
|
||||
|
||||
/// `product_price` is an out parameter.
|
||||
DbRes db_product_price_of_product(
|
||||
Db* db, ProductPrice* product_price, int64_t product_id);
|
||||
|
||||
/// `receipt.id`, `receipt.timestamp` and `receipt.products[i].id`
|
||||
/// are ignored.
|
||||
/// `id` is an out parameter.
|
||||
DbRes db_receipt_insert(Db* db, const Receipt* receipt, int64_t* id);
|
||||
|
@ -7,6 +7,13 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define REPORT_SQLITE3_ERROR() \
|
||||
fprintf(stderr, \
|
||||
"error: %s\n at %s:%d\n", \
|
||||
sqlite3_errmsg(connection), \
|
||||
__func__, \
|
||||
__LINE__)
|
||||
|
||||
static inline char* get_str_safe(sqlite3_stmt* stmt, int col)
|
||||
{
|
||||
const char* val = (const char*)sqlite3_column_text(stmt, col);
|
||||
@ -146,10 +153,8 @@ l0_return:
|
||||
return res;
|
||||
}
|
||||
|
||||
DbRes db_users_with_email(Db* db, Ids* ids, const char* email)
|
||||
DbRes db_user_with_email_exists(Db* db, bool* exists, const char* email)
|
||||
{
|
||||
static_assert(sizeof(User) == 40, "model has changed");
|
||||
|
||||
sqlite3* connection;
|
||||
CONNECT;
|
||||
DbRes res;
|
||||
@ -160,9 +165,9 @@ DbRes db_users_with_email(Db* db, Ids* ids, const char* email)
|
||||
connection, "SELECT id FROM users WHERE email = ?", -1, &stmt, NULL);
|
||||
sqlite3_bind_text(stmt, 1, email, -1, NULL);
|
||||
|
||||
while ((sqlite_res = sqlite3_step(stmt)) == SQLITE_ROW) {
|
||||
int64_t id = GET_INT(0);
|
||||
ids_push(ids, id);
|
||||
*exists = false;
|
||||
if ((sqlite_res = sqlite3_step(stmt)) == SQLITE_ROW) {
|
||||
*exists = true;
|
||||
}
|
||||
if (sqlite_res != SQLITE_DONE) {
|
||||
fprintf(stderr, "error: %s\n", sqlite3_errmsg(connection));
|
||||
@ -194,11 +199,7 @@ DbRes db_user_with_email(Db* db, User* user, const char* email)
|
||||
&stmt,
|
||||
NULL);
|
||||
if (prepare_res != SQLITE_OK) {
|
||||
fprintf(stderr,
|
||||
"error: %s\n at %s:%d\n",
|
||||
sqlite3_errmsg(connection),
|
||||
__func__,
|
||||
__LINE__);
|
||||
REPORT_SQLITE3_ERROR();
|
||||
res = DbRes_Error;
|
||||
goto l0_return;
|
||||
}
|
||||
@ -209,12 +210,7 @@ DbRes db_user_with_email(Db* db, User* user, const char* email)
|
||||
res = DbRes_NotFound;
|
||||
goto l0_return;
|
||||
} else if (step_res != SQLITE_ROW) {
|
||||
printf("step_res = %d, email = '%s'\n", step_res, email);
|
||||
fprintf(stderr,
|
||||
"error: %s\n at %s:%d\n",
|
||||
sqlite3_errmsg(connection),
|
||||
__func__,
|
||||
__LINE__);
|
||||
REPORT_SQLITE3_ERROR();
|
||||
res = DbRes_Error;
|
||||
goto l0_return;
|
||||
}
|
||||
@ -279,3 +275,212 @@ l0_return:
|
||||
DISCONNECT;
|
||||
return res;
|
||||
}
|
||||
|
||||
static inline DbRes get_product_price_from_product_id(
|
||||
sqlite3* connection, int64_t product_id, int64_t* price)
|
||||
{
|
||||
DbRes res;
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
int prepare_res = sqlite3_prepare_v2(connection,
|
||||
"SELECT price_dkk_cent 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, 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;
|
||||
}
|
||||
*price = GET_INT(0);
|
||||
|
||||
res = DbRes_Ok;
|
||||
l0_return:
|
||||
if (stmt)
|
||||
sqlite3_finalize(stmt);
|
||||
return res;
|
||||
}
|
||||
|
||||
static inline DbRes insert_product_price(sqlite3* connection,
|
||||
ProductPrice* product_price,
|
||||
int64_t product_id,
|
||||
int64_t price)
|
||||
{
|
||||
DbRes res;
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
int prepare_res = sqlite3_prepare_v2(connection,
|
||||
"INSERT INTO product_prices (product, price_dkk_cent) "
|
||||
"VALUES (?, ?)",
|
||||
-1,
|
||||
&stmt,
|
||||
NULL);
|
||||
if (prepare_res != SQLITE_OK) {
|
||||
REPORT_SQLITE3_ERROR();
|
||||
res = DbRes_Error;
|
||||
goto l0_return;
|
||||
}
|
||||
|
||||
sqlite3_bind_int64(stmt, 1, product_id);
|
||||
sqlite3_bind_int64(stmt, 2, price);
|
||||
|
||||
int step_res = sqlite3_step(stmt);
|
||||
if (step_res != SQLITE_DONE) {
|
||||
fprintf(stderr, "error: %s\n", sqlite3_errmsg(connection));
|
||||
res = DbRes_Error;
|
||||
goto l0_return;
|
||||
}
|
||||
|
||||
int64_t id = sqlite3_last_insert_rowid(connection);
|
||||
*product_price = (ProductPrice) {
|
||||
.id = id,
|
||||
.product_id = product_id,
|
||||
.price_dkk_cent = price,
|
||||
};
|
||||
|
||||
res = DbRes_Ok;
|
||||
l0_return:
|
||||
if (stmt)
|
||||
sqlite3_finalize(stmt);
|
||||
return res;
|
||||
}
|
||||
|
||||
DbRes db_product_price_of_product(
|
||||
Db* db, ProductPrice* product_price, int64_t product_id)
|
||||
{
|
||||
static_assert(sizeof(ProductPrice) == 24, "model has changed");
|
||||
|
||||
sqlite3* connection;
|
||||
CONNECT;
|
||||
DbRes res;
|
||||
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
int prepare_res;
|
||||
|
||||
int64_t current_price;
|
||||
res = get_product_price_from_product_id(
|
||||
connection, product_id, ¤t_price);
|
||||
if (res != DbRes_Ok) {
|
||||
goto l0_return;
|
||||
}
|
||||
|
||||
// find maybe existing product price
|
||||
prepare_res = sqlite3_prepare_v2(connection,
|
||||
"SELECT id FROM product_prices"
|
||||
" WHERE product = ? AND price_dkk_cent = ?",
|
||||
-1,
|
||||
&stmt,
|
||||
NULL);
|
||||
if (prepare_res != SQLITE_OK) {
|
||||
REPORT_SQLITE3_ERROR();
|
||||
res = DbRes_Error;
|
||||
goto l0_return;
|
||||
}
|
||||
sqlite3_bind_int64(stmt, 1, product_id);
|
||||
sqlite3_bind_int64(stmt, 2, current_price);
|
||||
|
||||
int step_res = sqlite3_step(stmt);
|
||||
if (step_res == SQLITE_ROW) {
|
||||
*product_price = (ProductPrice) {
|
||||
.id = GET_INT(0),
|
||||
.product_id = product_id,
|
||||
.price_dkk_cent = current_price,
|
||||
};
|
||||
} else if (step_res == SQLITE_DONE) {
|
||||
insert_product_price(
|
||||
connection, product_price, product_id, current_price);
|
||||
} else {
|
||||
REPORT_SQLITE3_ERROR();
|
||||
res = DbRes_Error;
|
||||
goto l0_return;
|
||||
}
|
||||
|
||||
res = DbRes_Ok;
|
||||
l0_return:
|
||||
if (stmt)
|
||||
sqlite3_finalize(stmt);
|
||||
DISCONNECT;
|
||||
return res;
|
||||
}
|
||||
|
||||
DbRes db_receipt_insert(Db* db, const Receipt* receipt, int64_t* id)
|
||||
{
|
||||
static_assert(sizeof(Receipt) == 48, "model has changed");
|
||||
static_assert(sizeof(ReceiptProduct) == 32, "model has changed");
|
||||
|
||||
sqlite3* connection;
|
||||
CONNECT;
|
||||
DbRes res;
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
int prepare_res = sqlite3_prepare_v2(connection,
|
||||
"INSERT INTO receipts (user, datetime) "
|
||||
"VALUES (?, unixepoch('now'))",
|
||||
-1,
|
||||
&stmt,
|
||||
NULL);
|
||||
|
||||
if (prepare_res != SQLITE_OK) {
|
||||
REPORT_SQLITE3_ERROR();
|
||||
res = DbRes_Error;
|
||||
goto l0_return;
|
||||
}
|
||||
|
||||
sqlite3_bind_int64(stmt, 1, receipt->user_id);
|
||||
|
||||
int step_res = sqlite3_step(stmt);
|
||||
if (step_res != SQLITE_DONE) {
|
||||
fprintf(stderr, "error: %s\n", sqlite3_errmsg(connection));
|
||||
res = DbRes_Error;
|
||||
goto l0_return;
|
||||
}
|
||||
|
||||
int64_t receipt_id = sqlite3_last_insert_rowid(connection);
|
||||
|
||||
if (id) {
|
||||
*id = receipt_id;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < receipt->products.size; ++i) {
|
||||
sqlite3_finalize(stmt);
|
||||
prepare_res = sqlite3_prepare_v2(connection,
|
||||
"INSERT INTO receipt_products (receipt, product_price, amount) "
|
||||
"VALUES (?, ?, ?)",
|
||||
-1,
|
||||
&stmt,
|
||||
NULL);
|
||||
|
||||
if (prepare_res != SQLITE_OK) {
|
||||
REPORT_SQLITE3_ERROR();
|
||||
res = DbRes_Error;
|
||||
goto l0_return;
|
||||
}
|
||||
|
||||
sqlite3_bind_int64(stmt, 1, receipt_id);
|
||||
sqlite3_bind_int64(stmt, 2, receipt->products.data[i].product_price_id);
|
||||
sqlite3_bind_int64(stmt, 3, receipt->products.data[i].amount);
|
||||
|
||||
int step_res = sqlite3_step(stmt);
|
||||
if (step_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;
|
||||
}
|
||||
|
@ -39,6 +39,20 @@ void product_price_destroy(ProductPrice* m)
|
||||
(void)m;
|
||||
}
|
||||
|
||||
void receipt_product_destroy(ReceiptProduct* m)
|
||||
{
|
||||
static_assert(sizeof(ReceiptProduct) == 32, "model has changed");
|
||||
|
||||
(void)m;
|
||||
}
|
||||
|
||||
void receipt_destroy(Receipt* m)
|
||||
{
|
||||
static_assert(sizeof(Receipt) == 48, "model has changed");
|
||||
|
||||
(void)m;
|
||||
}
|
||||
|
||||
void users_register_req_destroy(UsersRegisterReq* model)
|
||||
{
|
||||
static_assert(sizeof(UsersRegisterReq) == 24, "model has changed");
|
||||
@ -156,6 +170,43 @@ char* product_price_to_json_string(const ProductPrice* m)
|
||||
string_destroy(&string);
|
||||
return result;
|
||||
}
|
||||
char* receipt_to_json_string(const Receipt* m)
|
||||
{
|
||||
static_assert(sizeof(Receipt) == 48, "model has changed");
|
||||
|
||||
String string;
|
||||
string_construct(&string);
|
||||
string_pushf(&string,
|
||||
"{"
|
||||
"\"id\":%ld,"
|
||||
"\"user_id\":%ld,"
|
||||
"\"timestamp\":\"%s\","
|
||||
"\"products\":[",
|
||||
m->id,
|
||||
m->user_id,
|
||||
m->timestamp);
|
||||
|
||||
for (size_t i = 0; i < m->products.size; ++i) {
|
||||
if (i != 0) {
|
||||
string_pushf(&string, ",");
|
||||
}
|
||||
string_pushf(&string,
|
||||
"{"
|
||||
"\"id\":%ld,"
|
||||
"\"receipt_id\":%ld,"
|
||||
"\"product_price_id\":%ld,"
|
||||
"\"amount\":%ld"
|
||||
"}",
|
||||
m->products.data[i].id,
|
||||
m->products.data[i].receipt_id,
|
||||
m->products.data[i].product_price_id,
|
||||
m->products.data[i].amount);
|
||||
}
|
||||
string_pushf(&string, "]}");
|
||||
char* result = string_copy(&string);
|
||||
string_destroy(&string);
|
||||
return result;
|
||||
}
|
||||
|
||||
char* users_register_req_to_json(const UsersRegisterReq* m)
|
||||
{
|
||||
@ -321,6 +372,13 @@ int product_price_from_json(ProductPrice* m, const JsonValue* json)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int receipt_from_json(Receipt* m, const JsonValue* json)
|
||||
{
|
||||
static_assert(sizeof(Receipt) == 48, "model has changed");
|
||||
|
||||
PANIC("not implemented");
|
||||
}
|
||||
|
||||
int users_register_req_from_json(UsersRegisterReq* m, const JsonValue* json)
|
||||
{
|
||||
static_assert(sizeof(UsersRegisterReq) == 24, "model has changed");
|
||||
@ -396,4 +454,6 @@ int carts_purchase_req_from_json(CartsPurchaseReq* m, const JsonValue* json)
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_VEC_IMPL(ProductPrice, ProductPriceVec, product_price_vec, )
|
||||
DEFINE_VEC_IMPL(ReceiptProduct, ReceiptProductVec, receipt_product_vec, )
|
||||
DEFINE_VEC_IMPL(CartsItem, CartsItemVec, carts_item_vec, )
|
||||
|
@ -32,10 +32,30 @@ typedef struct {
|
||||
int64_t price_dkk_cent;
|
||||
} ProductPrice;
|
||||
|
||||
DECLARE_VEC_TYPE(ProductPrice, ProductPriceVec, product_price_vec, )
|
||||
|
||||
typedef struct {
|
||||
int64_t id;
|
||||
int64_t receipt_id;
|
||||
int64_t product_price_id;
|
||||
int64_t amount;
|
||||
} ReceiptProduct;
|
||||
|
||||
DECLARE_VEC_TYPE(ReceiptProduct, ReceiptProductVec, receipt_product_vec, )
|
||||
|
||||
typedef struct {
|
||||
int64_t id;
|
||||
int64_t user_id;
|
||||
char* timestamp;
|
||||
ReceiptProductVec products;
|
||||
} Receipt;
|
||||
|
||||
void user_destroy(User* model);
|
||||
void coord_destroy(Coord* model);
|
||||
void product_destroy(Product* model);
|
||||
void product_price_destroy(ProductPrice* model);
|
||||
void receipt_product_destroy(ReceiptProduct* model);
|
||||
void receipt_destroy(Receipt* model);
|
||||
|
||||
//
|
||||
|
||||
|
@ -9,6 +9,7 @@ DEFINE_MODEL_JSON(User, user)
|
||||
DEFINE_MODEL_JSON(Coord, coord)
|
||||
DEFINE_MODEL_JSON(Product, product)
|
||||
DEFINE_MODEL_JSON(ProductPrice, product_price)
|
||||
DEFINE_MODEL_JSON(Receipt, receipt)
|
||||
|
||||
DEFINE_MODEL_JSON(UsersRegisterReq, users_register_req)
|
||||
DEFINE_MODEL_JSON(SessionsLoginReq, sessions_login_req)
|
||||
|
@ -70,12 +70,15 @@ async function testCarts(t: Deno.TestContext, token: string) {
|
||||
items: [
|
||||
{ product_id: 1, amount: 2 },
|
||||
{ product_id: 2, amount: 5 },
|
||||
{ product_id: 2, amount: 5 },
|
||||
{ product_id: 2, amount: 5 },
|
||||
{ product_id: 2, amount: 5 },
|
||||
],
|
||||
},
|
||||
{ "Session-Token": token },
|
||||
);
|
||||
|
||||
assertEquals(res, { ok: true });
|
||||
assertEquals(res.ok, true);
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user