mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-04-27 16:24:07 +02:00
get one receipt
This commit is contained in:
parent
f799db5b32
commit
2d74a872fe
@ -45,14 +45,17 @@ void route_post_sessions_login(HttpCtx* ctx);
|
||||
void route_post_sessions_logout(HttpCtx* ctx);
|
||||
void route_get_sessions_user(HttpCtx* ctx);
|
||||
|
||||
void route_get_receipt(HttpCtx* ctx);
|
||||
|
||||
const Session* header_session(HttpCtx* ctx);
|
||||
const Session* middleware_session(HttpCtx* ctx);
|
||||
|
||||
#define RESPOND(HTTP_CTX, STATUS, MIME_TYPE, ...) \
|
||||
{ \
|
||||
HttpCtx* _ctx = (HTTP_CTX); \
|
||||
char _body[8192]; \
|
||||
snprintf(_body, 8192 - 1, __VA_ARGS__); \
|
||||
size_t _body_size = (size_t)snprintf(NULL, 0, __VA_ARGS__); \
|
||||
char* _body = calloc(_body_size + 1, sizeof(char)); \
|
||||
sprintf(_body, __VA_ARGS__); \
|
||||
\
|
||||
char content_length[24] = { 0 }; \
|
||||
snprintf(content_length, 24 - 1, "%ld", strlen(_body)); \
|
||||
@ -61,6 +64,7 @@ const Session* middleware_session(HttpCtx* ctx);
|
||||
http_ctx_res_headers_set(_ctx, "Content-Length", content_length); \
|
||||
\
|
||||
http_ctx_respond(_ctx, (STATUS), _body); \
|
||||
free(_body); \
|
||||
}
|
||||
|
||||
#define RESPOND_HTML(HTTP_CTX, STATUS, ...) \
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "../http/http.h"
|
||||
#include "../models/models_json.h"
|
||||
#include "controllers.h"
|
||||
#include <string.h>
|
||||
|
||||
void route_get_index(HttpCtx* ctx)
|
||||
{
|
||||
@ -40,6 +41,16 @@ l0_return:
|
||||
|
||||
void route_get_not_found(HttpCtx* ctx)
|
||||
{
|
||||
if (http_ctx_req_headers_has(ctx, "Accept")) {
|
||||
const char* accept = http_ctx_req_headers_get(ctx, "Accept");
|
||||
if (strcmp(accept, "application/json") == 0) {
|
||||
RESPOND_JSON(ctx,
|
||||
404,
|
||||
"{\"ok\":false,\"msg\":\"404 Not Found\",\"path\":\"%s\"}",
|
||||
http_ctx_req_path(ctx));
|
||||
return;
|
||||
}
|
||||
}
|
||||
RESPOND_HTML(ctx,
|
||||
404,
|
||||
"<!DOCTYPE html><html><head><meta "
|
||||
|
107
backend/src/controllers/receipts.c
Normal file
107
backend/src/controllers/receipts.c
Normal file
@ -0,0 +1,107 @@
|
||||
#include "../models/models_json.h"
|
||||
#include "../util/str.h"
|
||||
#include "controllers.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
StrSlice key;
|
||||
StrSlice value;
|
||||
} QueryParamEntry;
|
||||
|
||||
DEFINE_VEC(QueryParamEntry, QueryParamVec, query_param_vec)
|
||||
|
||||
typedef struct {
|
||||
QueryParamVec vec;
|
||||
} QueryParams;
|
||||
|
||||
QueryParams parse_query_params(const char* query)
|
||||
{
|
||||
QueryParams result = {
|
||||
.vec = (QueryParamVec) { 0 },
|
||||
};
|
||||
query_param_vec_construct(&result.vec);
|
||||
|
||||
StrSplitter params = str_splitter(query, strlen(query), "&");
|
||||
StrSlice param;
|
||||
while ((param = str_split_next(¶ms)).len != 0) {
|
||||
StrSplitter left_right = str_splitter(param.ptr, param.len, "=");
|
||||
StrSlice key = str_split_next(&left_right);
|
||||
StrSlice value = str_split_next(&left_right);
|
||||
|
||||
query_param_vec_push(&result.vec, (QueryParamEntry) { key, value });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void query_params_destroy(QueryParams* query_params)
|
||||
{
|
||||
query_param_vec_destroy(&query_params->vec);
|
||||
}
|
||||
|
||||
const char* query_params_get(const QueryParams* query_params, const char* key)
|
||||
{
|
||||
size_t key_len = strlen(key);
|
||||
for (size_t i = 0; i < query_params->vec.size; ++i) {
|
||||
const QueryParamEntry* entry = &query_params->vec.data[i];
|
||||
if (key_len == entry->key.len && strcmp(key, entry->key.ptr)) {
|
||||
return str_slice_copy(&entry->value);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void route_get_receipt(HttpCtx* ctx)
|
||||
{
|
||||
Cx* cx = http_ctx_user_ctx(ctx);
|
||||
const Session* session = middleware_session(ctx);
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
const char* query = http_ctx_req_query(ctx);
|
||||
QueryParams params = parse_query_params(query);
|
||||
const char* receipt_id_str = query_params_get(¶ms, "receipt_id");
|
||||
query_params_destroy(¶ms);
|
||||
if (!receipt_id_str) {
|
||||
RESPOND_BAD_REQUEST(ctx, "no receipt_id parameter");
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t receipt_id = strtol(receipt_id_str, NULL, 10);
|
||||
|
||||
Receipt receipt;
|
||||
DbRes db_rizz = db_receipt_with_id(cx->db, &receipt, receipt_id);
|
||||
if (db_rizz != 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);
|
||||
}
|
||||
string_pushf(&res, "]}");
|
||||
|
||||
RESPOND_JSON(ctx, 200, "%s", res.data);
|
||||
string_destroy(&res);
|
||||
product_price_vec_destroy(&product_prices);
|
||||
receipt_destroy(&receipt);
|
||||
}
|
@ -37,3 +37,11 @@ DbRes db_product_price_of_product(
|
||||
/// are ignored.
|
||||
/// `id` is an out parameter.
|
||||
DbRes db_receipt_insert(Db* db, const Receipt* receipt, int64_t* id);
|
||||
|
||||
/// `receipt` field is an out parameter.
|
||||
DbRes db_receipt_with_id(Db* db, Receipt* receipt, int64_t id);
|
||||
|
||||
/// `product_prices` field is an out parameter.
|
||||
/// Expects `product_prices` to be constructed.
|
||||
DbRes db_receipt_prices(
|
||||
Db* db, ProductPriceVec* product_prices, int64_t receipt_id);
|
||||
|
@ -373,7 +373,6 @@ DbRes db_product_price_of_product(
|
||||
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 = ?",
|
||||
@ -484,3 +483,132 @@ l0_return:
|
||||
DISCONNECT;
|
||||
return res;
|
||||
}
|
||||
|
||||
DbRes db_receipt_with_id(Db* db, Receipt* receipt, int64_t id)
|
||||
{
|
||||
static_assert(sizeof(Receipt) == 48, "model has changed");
|
||||
|
||||
sqlite3* connection;
|
||||
CONNECT;
|
||||
DbRes res;
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
|
||||
int prepare_res = sqlite3_prepare_v2(connection,
|
||||
"SELECT id, user, datetime(datetime) FROM receipts 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;
|
||||
}
|
||||
|
||||
*receipt = (Receipt) {
|
||||
.id = GET_INT(0),
|
||||
.user_id = GET_INT(1),
|
||||
.timestamp = GET_STR(2),
|
||||
.products = (ReceiptProductVec) { 0 },
|
||||
};
|
||||
|
||||
receipt_product_vec_construct(&receipt->products);
|
||||
|
||||
prepare_res = sqlite3_prepare_v2(connection,
|
||||
"SELECT id, receipt, product_price, amount FROM receipt_products"
|
||||
" WHERE 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) {
|
||||
ReceiptProduct product = {
|
||||
.id = GET_INT(0),
|
||||
.receipt_id = GET_INT(1),
|
||||
.product_price_id = GET_INT(2),
|
||||
.amount = GET_INT(3),
|
||||
};
|
||||
receipt_product_vec_push(&receipt->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;
|
||||
}
|
||||
|
||||
DbRes db_receipt_prices(
|
||||
Db* db, ProductPriceVec* product_prices, int64_t receipt_id)
|
||||
{
|
||||
static_assert(sizeof(ProductPrice) == 24, "model has changed");
|
||||
|
||||
sqlite3* connection;
|
||||
CONNECT;
|
||||
DbRes res;
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
|
||||
int prepare_res = sqlite3_prepare_v2(connection,
|
||||
"SELECT product_prices.id, product_prices.product,"
|
||||
" product_prices.price_dkk_cent"
|
||||
" FROM receipt_products JOIN product_prices"
|
||||
" ON product_prices.id = product_price"
|
||||
" AND 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) {
|
||||
ProductPrice product_price = {
|
||||
.id = GET_INT(0),
|
||||
.product_id = GET_INT(1),
|
||||
.price_dkk_cent = GET_INT(2),
|
||||
};
|
||||
product_price_vec_push(product_prices, product_price);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -143,12 +143,12 @@ static inline int parse_request_header(Client* client, Request* request)
|
||||
char* query = NULL;
|
||||
if (path_len < uri_str.len) {
|
||||
size_t query_len = 0;
|
||||
while (path_len + query_len < uri_str.len
|
||||
while (path_len + query_len + 1 < uri_str.len
|
||||
&& uri_str.ptr[path_len + query_len] != '#') {
|
||||
query_len += 1;
|
||||
}
|
||||
query = calloc(query_len + 1, sizeof(char));
|
||||
strncpy(query, &uri_str.ptr[path_len], query_len);
|
||||
strncpy(query, &uri_str.ptr[path_len + 1], query_len);
|
||||
query[query_len] = '\0';
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,7 @@ void* http_ctx_user_ctx(HttpCtx* ctx);
|
||||
const char* http_ctx_req_path(HttpCtx* ctx);
|
||||
bool http_ctx_req_headers_has(HttpCtx* ctx, const char* key);
|
||||
const char* http_ctx_req_headers_get(HttpCtx* ctx, const char* key);
|
||||
const char* http_ctx_req_query(HttpCtx* ctx);
|
||||
const char* http_ctx_req_body(HttpCtx* ctx);
|
||||
void http_ctx_res_headers_set(HttpCtx* ctx, const char* key, const char* value);
|
||||
void http_ctx_respond(HttpCtx* ctx, int status, const char* body);
|
||||
|
@ -155,6 +155,11 @@ const char* http_ctx_req_headers_get(HttpCtx* ctx, const char* key)
|
||||
return http_request_get_header(ctx->req, key);
|
||||
}
|
||||
|
||||
const char* http_ctx_req_query(HttpCtx* ctx)
|
||||
{
|
||||
return ctx->req->query;
|
||||
}
|
||||
|
||||
const char* http_ctx_req_body(HttpCtx* ctx)
|
||||
{
|
||||
return ctx->req_body;
|
||||
|
@ -41,6 +41,8 @@ int main(void)
|
||||
|
||||
http_server_post(server, "/api/carts/purchase", route_post_carts_purchase);
|
||||
|
||||
http_server_get(server, "/api/receipts/one", route_get_receipt);
|
||||
|
||||
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(
|
||||
|
@ -14,6 +14,14 @@ char* str_dup(const char* str)
|
||||
return clone;
|
||||
}
|
||||
|
||||
const char* str_slice_copy(const StrSlice* slice)
|
||||
{
|
||||
char* copy = malloc(slice->len + 1);
|
||||
strncpy(copy, slice->ptr, slice->len);
|
||||
copy[slice->len] = '\0';
|
||||
return copy;
|
||||
}
|
||||
|
||||
StrSplitter str_splitter(const char* text, size_t text_len, const char* split)
|
||||
{
|
||||
return (StrSplitter) {
|
||||
@ -36,10 +44,12 @@ StrSlice str_split_next(StrSplitter* splitter)
|
||||
return (StrSlice) { ptr, len };
|
||||
}
|
||||
}
|
||||
return (StrSlice) {
|
||||
StrSlice slice = {
|
||||
.ptr = &splitter->text[splitter->i],
|
||||
.len = splitter->text_len - splitter->i,
|
||||
};
|
||||
splitter->i = splitter->text_len;
|
||||
return slice;
|
||||
}
|
||||
|
||||
void string_push_str(String* string, const char* str)
|
||||
|
@ -12,6 +12,8 @@ typedef struct {
|
||||
size_t len;
|
||||
} StrSlice;
|
||||
|
||||
const char* str_slice_copy(const StrSlice* slice);
|
||||
|
||||
typedef struct {
|
||||
const char* text;
|
||||
size_t text_len;
|
||||
|
@ -49,7 +49,7 @@ Deno.test("test backend", async (t) => {
|
||||
// console.log(sessionUserRes.user);
|
||||
});
|
||||
|
||||
await testCarts(t, token);
|
||||
await testCartsAndReceipts(t, token);
|
||||
|
||||
await t.step("test /api/sessions/logout", async () => {
|
||||
const logoutRes = await post<{ ok: boolean }>(
|
||||
@ -62,9 +62,11 @@ Deno.test("test backend", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
async function testCarts(t: Deno.TestContext, token: string) {
|
||||
async function testCartsAndReceipts(t: Deno.TestContext, token: string) {
|
||||
let receiptId: number | undefined = undefined;
|
||||
|
||||
await t.step("test /api/carts/purchase", async () => {
|
||||
const res = await post<{ ok: boolean }>(
|
||||
const res = await post<{ ok: boolean; receipt_id: number }>(
|
||||
"/api/carts/purchase",
|
||||
{
|
||||
items: [
|
||||
@ -76,6 +78,21 @@ async function testCarts(t: Deno.TestContext, token: string) {
|
||||
);
|
||||
|
||||
assertEquals(res.ok, true);
|
||||
receiptId = res.receipt_id;
|
||||
});
|
||||
|
||||
if (!receiptId) {
|
||||
return;
|
||||
}
|
||||
|
||||
await t.step("test /api/receipts/one", async () => {
|
||||
const res = await get<{ ok: boolean }>(
|
||||
`/api/receipts/one?receipt_id=${receiptId}`,
|
||||
{ "Session-Token": token },
|
||||
);
|
||||
|
||||
console.log(res);
|
||||
assertEquals(res.ok, true);
|
||||
});
|
||||
}
|
||||
|
||||
@ -83,7 +100,9 @@ function get<Res>(
|
||||
path: string,
|
||||
headers: Record<string, string>,
|
||||
): Promise<Res> {
|
||||
return fetch(`${url}${path}`, { headers })
|
||||
return fetch(`${url}${path}`, {
|
||||
headers: { ...headers, "Accept": "application/json" },
|
||||
})
|
||||
.then((res) => res.json());
|
||||
}
|
||||
|
||||
@ -94,7 +113,11 @@ function post<Res, Req = unknown>(
|
||||
): Promise<Res> {
|
||||
return fetch(`${url}${path}`, {
|
||||
method: "post",
|
||||
headers: { ...headers, "Content-Type": "application/json" },
|
||||
headers: {
|
||||
...headers,
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
}).then((res) => res.json());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user