mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-04-27 16:24:07 +02:00
product images
This commit is contained in:
parent
e494277978
commit
a7591a96ff
@ -51,6 +51,14 @@ CREATE TABLE IF NOT EXISTS receipt_products (
|
||||
FOREIGN KEY(product_price) REFERENCES product_prices(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS product_images (
|
||||
id INTEGER PRIMARY KEY,
|
||||
product INTEGER NOT NULL UNIQUE,
|
||||
data BLOB NOT NULL,
|
||||
|
||||
FOREIGN KEY(product) REFERENCES products(id)
|
||||
);
|
||||
|
||||
INSERT OR REPLACE INTO users (name, email, password_hash, balance_dkk_cent)
|
||||
VALUES ('User','test@email.com','08ce0220f6d63d85c3ac313e308f4fca35ecfb850baa8ddb924cfab98137b6b18b4a8e027067cb98802757df1337246a0f3aa25c44c2b788517a871086419dcf',10000);
|
||||
|
||||
|
9
backend/public/deno.jsonc
Normal file
9
backend/public/deno.jsonc
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"checkJs": false,
|
||||
"lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"]
|
||||
},
|
||||
"fmt": {
|
||||
"indentWidth": 4
|
||||
}
|
||||
}
|
@ -59,22 +59,36 @@
|
||||
#editor input, #editor textarea {
|
||||
width: 250px;
|
||||
}
|
||||
#wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
#wrapper form {
|
||||
min-width: 500px;
|
||||
}
|
||||
#image-uploader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
}
|
||||
#image-uploader #preview {
|
||||
height: 150px;
|
||||
background-color: #fff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Products</h2>
|
||||
<table id="product-list">
|
||||
</table>
|
||||
<div id="wrapper">
|
||||
<fieldset>
|
||||
<legend>Editor</legend>
|
||||
|
||||
<form id="editor">
|
||||
<center>
|
||||
<button id="load">Load</button>
|
||||
<button id="save">Save</button>
|
||||
<button id="new">New</button>
|
||||
</center>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><label for="product-id">id: </label></td>
|
||||
@ -103,7 +117,39 @@
|
||||
<td><input id="product-barcode" type="text"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<center>
|
||||
<button id="load">Load</button>
|
||||
<button id="save">Save</button>
|
||||
<button id="new">New</button>
|
||||
</center>
|
||||
</form>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Image uploader</legend>
|
||||
|
||||
<form id="image-uploader">
|
||||
<table>
|
||||
<tr>
|
||||
<td><label for="product-id">id: </label></td>
|
||||
<td>
|
||||
<input id="product-id" type="text">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="image-file">image: </label></td>
|
||||
<td>
|
||||
<input
|
||||
id="file"
|
||||
type="file"
|
||||
accept="image/png" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div><img id="preview" src="" alt=""></div>
|
||||
<button id="save">Save</button>
|
||||
</form>
|
||||
</fieldset>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
const productList = document.querySelector("#product-list");
|
||||
const editor = {
|
||||
form: document.querySelector("#editor"),
|
||||
@ -8,9 +7,16 @@ const editor = {
|
||||
idInput: document.querySelector("#editor #product-id"),
|
||||
nameInput: document.querySelector("#editor #product-name"),
|
||||
priceInput: document.querySelector("#editor #product-price"),
|
||||
descriptionTextarea: document.querySelector("#editor #product-description"),
|
||||
coordInput: document.querySelector("#editor #product-coord"),
|
||||
barcodeInput: document.querySelector("#editor #product-barcode"),
|
||||
descriptionTextarea: document.querySelector("#editor #product-description"),
|
||||
};
|
||||
const imageUploader = {
|
||||
form: document.querySelector("#image-uploader"),
|
||||
idInput: document.querySelector("#image-uploader #product-id"),
|
||||
saveButton: document.querySelector("#image-uploader #save"),
|
||||
preview: document.querySelector("#image-uploader #preview"),
|
||||
fileInput: document.querySelector("#image-uploader #file"),
|
||||
};
|
||||
|
||||
let products = [];
|
||||
@ -28,10 +34,12 @@ function selectProduct(product) {
|
||||
editor.barcodeInput.value = product.barcode.toString();
|
||||
}
|
||||
|
||||
async function loadProduct() {
|
||||
function loadProduct() {
|
||||
selectedProductId = parseInt(editor.idInput.value);
|
||||
|
||||
const product = products.find(product => product.id === selectedProductId);
|
||||
const product = products.find((product) =>
|
||||
product.id === selectedProductId
|
||||
);
|
||||
if (!product) {
|
||||
alert(`no product with id ${selectedProductId}`);
|
||||
return;
|
||||
@ -48,7 +56,7 @@ function productFromForm() {
|
||||
price_dkk_cent: Math.floor(parseFloat(editor.priceInput.value) * 100),
|
||||
coord_id: parseInt(editor.coordInput.value),
|
||||
barcode: editor.barcodeInput.value,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function saveProduct() {
|
||||
@ -57,10 +65,9 @@ async function saveProduct() {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(product),
|
||||
}).then(res => res.json());
|
||||
}).then((res) => res.json());
|
||||
|
||||
await updateProductList();
|
||||
|
||||
}
|
||||
|
||||
async function newProduct() {
|
||||
@ -69,14 +76,14 @@ async function newProduct() {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(product),
|
||||
}).then(res => res.json());
|
||||
}).then((res) => res.json());
|
||||
|
||||
await updateProductList();
|
||||
}
|
||||
|
||||
async function updateProductList() {
|
||||
const res = await fetch("/api/products/all")
|
||||
.then(res => res.json());
|
||||
.then((res) => res.json());
|
||||
|
||||
products = res.products;
|
||||
|
||||
@ -92,7 +99,7 @@ async function updateProductList() {
|
||||
</tr>
|
||||
`;
|
||||
productList.innerHTML += products
|
||||
.map(product => `
|
||||
.map((product) => `
|
||||
<tr>
|
||||
<td><code>${product.id}</code></td>
|
||||
<td><strong>${product.name}</strong></td>
|
||||
@ -121,15 +128,45 @@ editor.form
|
||||
e.preventDefault();
|
||||
});
|
||||
editor.loadButton
|
||||
.addEventListener("click", (e) => {
|
||||
.addEventListener("click", (_e) => {
|
||||
loadProduct();
|
||||
});
|
||||
editor.saveButton
|
||||
.addEventListener("click", (e) => {
|
||||
.addEventListener("click", (_e) => {
|
||||
saveProduct();
|
||||
});
|
||||
editor.newButton
|
||||
.addEventListener("click", (e) => {
|
||||
.addEventListener("click", (_e) => {
|
||||
newProduct();
|
||||
});
|
||||
|
||||
imageUploader.form
|
||||
.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
imageUploader.fileInput
|
||||
.addEventListener("input", (e) => {
|
||||
console.log(e);
|
||||
const image = imageUploader.fileInput.files[0];
|
||||
const data = URL.createObjectURL(image);
|
||||
imageUploader.preview.src = data;
|
||||
});
|
||||
imageUploader.saveButton
|
||||
.addEventListener("click", async (_e) => {
|
||||
const id = parseInt(imageUploader.idInput.value);
|
||||
const image = imageUploader.fileInput.files[0];
|
||||
|
||||
const buffer = await new Promise((resolve) => {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener("loadend", () => {
|
||||
resolve(reader.result);
|
||||
});
|
||||
reader.readAsArrayBuffer(image);
|
||||
});
|
||||
|
||||
await fetch(`/api/products/set-image?product_id=${id}`, {
|
||||
method: "post",
|
||||
headers: { "Content-Type": image.type },
|
||||
body: buffer,
|
||||
}).then((res) => res.json());
|
||||
});
|
||||
|
BIN
backend/public/product_fallback_256x256.png
Normal file
BIN
backend/public/product_fallback_256x256.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
BIN
backend/public/product_fallback_512x512.png
Normal file
BIN
backend/public/product_fallback_512x512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.3 KiB |
@ -10,7 +10,7 @@ void route_post_carts_purchase(HttpCtx* ctx)
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
const char* body_str = http_ctx_req_body(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");
|
||||
|
@ -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_post_products_set_image(HttpCtx* ctx);
|
||||
void route_get_products_image_png(HttpCtx* ctx);
|
||||
|
||||
void route_get_product_editor_html(HttpCtx* ctx);
|
||||
void route_get_product_editor_js(HttpCtx* ctx);
|
||||
@ -68,9 +70,8 @@ const Session* middleware_session(HttpCtx* ctx);
|
||||
snprintf(content_length, 24 - 1, "%ld", strlen(_body)); \
|
||||
\
|
||||
http_ctx_res_headers_set(_ctx, "Content-Type", MIME_TYPE); \
|
||||
http_ctx_res_headers_set(_ctx, "Content-Length", content_length); \
|
||||
\
|
||||
http_ctx_respond(_ctx, (STATUS), _body); \
|
||||
http_ctx_respond_str(_ctx, (STATUS), _body); \
|
||||
free(_body); \
|
||||
}
|
||||
|
||||
@ -84,6 +85,14 @@ const Session* middleware_session(HttpCtx* ctx);
|
||||
#define RESPOND_SERVER_ERROR(HTTP_CTX) \
|
||||
RESPOND_JSON(HTTP_CTX, 500, "{\"ok\":false,\"msg\":\"server error\"}")
|
||||
|
||||
#define RESPOND_HTML_BAD_REQUEST(CTX, ...) \
|
||||
RESPOND_HTML(CTX, \
|
||||
500, \
|
||||
"<!DOCTYPE html><html><head><meta " \
|
||||
"charset=\"utf-8\"></head><body><center><h1>400 Bad " \
|
||||
"Request</h1><p>%s</p></body></html>", \
|
||||
__VA_ARGS__);
|
||||
|
||||
#define RESPOND_HTML_SERVER_ERROR(CTX) \
|
||||
RESPOND_HTML(CTX, \
|
||||
500, \
|
||||
|
@ -18,7 +18,7 @@ void route_post_set_number(HttpCtx* ctx)
|
||||
{
|
||||
Cx* cx = http_ctx_user_ctx(ctx);
|
||||
|
||||
const char* body_text = http_ctx_req_body(ctx);
|
||||
const char* body_text = http_ctx_req_body_str(ctx);
|
||||
JsonParser parser;
|
||||
json_parser_construct(&parser, body_text, strlen(body_text));
|
||||
JsonValue* body = json_parser_parse(&parser);
|
||||
|
@ -40,7 +40,7 @@ void route_post_products_create(HttpCtx* ctx)
|
||||
{
|
||||
Cx* cx = http_ctx_user_ctx(ctx);
|
||||
|
||||
const char* body_str = http_ctx_req_body(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");
|
||||
@ -81,7 +81,7 @@ void route_post_products_update(HttpCtx* ctx)
|
||||
{
|
||||
Cx* cx = http_ctx_user_ctx(ctx);
|
||||
|
||||
const char* body_str = http_ctx_req_body(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));
|
||||
@ -113,10 +113,126 @@ l0_return:
|
||||
product_destroy(&product);
|
||||
}
|
||||
|
||||
static inline int read_and_send_file(HttpCtx* ctx,
|
||||
const char* filepath,
|
||||
size_t max_file_size,
|
||||
const char* mime_type)
|
||||
void route_post_products_set_image(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);
|
||||
|
||||
const uint8_t* body = http_ctx_req_body(ctx);
|
||||
size_t body_size = http_ctx_req_body_size(ctx);
|
||||
|
||||
DbRes db_res = db_product_image_insert(cx->db, product_id, body, body_size);
|
||||
if (db_res != DbRes_Ok) {
|
||||
RESPOND_SERVER_ERROR(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
RESPOND_JSON(ctx, 200, "{\"ok\":true}");
|
||||
}
|
||||
|
||||
static inline int read_fallback_image(uint8_t** buffer, size_t* buffer_size)
|
||||
{
|
||||
int res;
|
||||
|
||||
const char* filepath = PUBLIC_DIR_PATH "/product_fallback_256x256.png";
|
||||
|
||||
FILE* fp = fopen(filepath, "r");
|
||||
if (!fp) {
|
||||
return -1;
|
||||
}
|
||||
fseek(fp, 0L, SEEK_END);
|
||||
size_t file_size = (size_t)ftell(fp);
|
||||
rewind(fp);
|
||||
|
||||
const size_t max_file_size = 16777216;
|
||||
if (file_size >= max_file_size) {
|
||||
fprintf(stderr,
|
||||
"error: file too large '%s' >= %ld\n",
|
||||
filepath,
|
||||
max_file_size);
|
||||
res = -1;
|
||||
goto l0_return;
|
||||
}
|
||||
|
||||
uint8_t* temp_buffer = malloc(file_size);
|
||||
size_t bytes_read = fread(temp_buffer, sizeof(char), file_size, fp);
|
||||
if (bytes_read != file_size) {
|
||||
fprintf(stderr, "error: could not read file '%s'\n", filepath);
|
||||
res = -1;
|
||||
goto l1_return;
|
||||
}
|
||||
|
||||
*buffer = temp_buffer;
|
||||
*buffer_size = file_size;
|
||||
temp_buffer = NULL;
|
||||
|
||||
res = 0;
|
||||
l1_return:
|
||||
if (temp_buffer)
|
||||
free(temp_buffer);
|
||||
l0_return:
|
||||
fclose(fp);
|
||||
return res;
|
||||
}
|
||||
|
||||
void route_get_products_image_png(HttpCtx* ctx)
|
||||
{
|
||||
Cx* cx = http_ctx_user_ctx(ctx);
|
||||
|
||||
const char* query = http_ctx_req_query(ctx);
|
||||
if (!query) {
|
||||
RESPOND_HTML_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_HTML_BAD_REQUEST(ctx, "no product_id parameter");
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t product_id = strtol(product_id_str, NULL, 10);
|
||||
free(product_id_str);
|
||||
|
||||
uint8_t* buffer;
|
||||
size_t buffer_size;
|
||||
|
||||
DbRes db_res = db_product_image_with_product_id(
|
||||
cx->db, &buffer, &buffer_size, product_id);
|
||||
if (db_res == DbRes_NotFound) {
|
||||
int res = read_fallback_image(&buffer, &buffer_size);
|
||||
if (res != 0) {
|
||||
RESPOND_HTML_SERVER_ERROR(ctx);
|
||||
return;
|
||||
}
|
||||
} else if (db_res != DbRes_Ok) {
|
||||
RESPOND_HTML_SERVER_ERROR(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
http_ctx_res_headers_set(ctx, "Content-Type", "image/png");
|
||||
|
||||
http_ctx_respond(ctx, 200, buffer, buffer_size);
|
||||
}
|
||||
|
||||
static inline int read_and_send_file(
|
||||
HttpCtx* ctx, const char* filepath, const char* mime_type)
|
||||
{
|
||||
int res;
|
||||
|
||||
@ -125,14 +241,12 @@ static inline int read_and_send_file(HttpCtx* ctx,
|
||||
RESPOND_HTML_SERVER_ERROR(ctx);
|
||||
return -1;
|
||||
}
|
||||
fseek(fp, 0L, SEEK_END);
|
||||
size_t file_size = (size_t)ftell(fp);
|
||||
rewind(fp);
|
||||
|
||||
char* buf = calloc(max_file_size + 1, sizeof(char));
|
||||
size_t bytes_read = fread(buf, sizeof(char), max_file_size, fp);
|
||||
if (bytes_read == 0) {
|
||||
RESPOND_HTML_SERVER_ERROR(ctx);
|
||||
res = -1;
|
||||
goto l0_return;
|
||||
} else if (bytes_read >= max_file_size) {
|
||||
const size_t max_file_size = 16777216;
|
||||
if (file_size >= max_file_size) {
|
||||
fprintf(stderr,
|
||||
"error: file too large '%s' >= %ld\n",
|
||||
filepath,
|
||||
@ -142,13 +256,18 @@ static inline int read_and_send_file(HttpCtx* ctx,
|
||||
goto l0_return;
|
||||
}
|
||||
|
||||
char content_length[24] = { 0 };
|
||||
snprintf(content_length, 24 - 1, "%ld", bytes_read);
|
||||
char* buf = calloc(file_size + 1, sizeof(char));
|
||||
size_t bytes_read = fread(buf, sizeof(char), file_size, fp);
|
||||
if (bytes_read != file_size) {
|
||||
fprintf(stderr, "error: could not read file '%s'\n", filepath);
|
||||
RESPOND_HTML_SERVER_ERROR(ctx);
|
||||
res = -1;
|
||||
goto l0_return;
|
||||
}
|
||||
|
||||
http_ctx_res_headers_set(ctx, "Content-Type", mime_type);
|
||||
http_ctx_res_headers_set(ctx, "Content-Length", content_length);
|
||||
|
||||
http_ctx_respond(ctx, 200, buf);
|
||||
http_ctx_respond_str(ctx, 200, buf);
|
||||
|
||||
res = 0;
|
||||
l0_return:
|
||||
@ -159,13 +278,11 @@ l0_return:
|
||||
void route_get_product_editor_html(HttpCtx* ctx)
|
||||
{
|
||||
read_and_send_file(
|
||||
ctx, PUBLIC_DIR_PATH "/product_editor.html", 16384 - 1, "text/html");
|
||||
ctx, PUBLIC_DIR_PATH "/product_editor.html", "text/html");
|
||||
}
|
||||
|
||||
void route_get_product_editor_js(HttpCtx* ctx)
|
||||
{
|
||||
read_and_send_file(ctx,
|
||||
PUBLIC_DIR_PATH "/product_editor.js",
|
||||
16384 - 1,
|
||||
"application/javascript");
|
||||
read_and_send_file(
|
||||
ctx, PUBLIC_DIR_PATH "/product_editor.js", "application/javascript");
|
||||
}
|
||||
|
@ -12,6 +12,10 @@ void route_get_receipts_one(HttpCtx* ctx)
|
||||
return;
|
||||
|
||||
const char* query = http_ctx_req_query(ctx);
|
||||
if (!query) {
|
||||
RESPOND_BAD_REQUEST(ctx, "no receipt_id parameter");
|
||||
return;
|
||||
}
|
||||
HttpQueryParams* params = http_parse_query_params(query);
|
||||
char* receipt_id_str = http_query_params_get(params, "receipt_id");
|
||||
http_query_params_free(params);
|
||||
|
@ -8,7 +8,7 @@ void route_post_sessions_login(HttpCtx* ctx)
|
||||
{
|
||||
Cx* cx = http_ctx_user_ctx(ctx);
|
||||
|
||||
const char* body_str = http_ctx_req_body(ctx);
|
||||
const char* body_str = http_ctx_req_body_str(ctx);
|
||||
|
||||
JsonValue* body_json = json_parse(body_str, strlen(body_str));
|
||||
|
||||
|
@ -8,7 +8,7 @@ void route_post_users_register(HttpCtx* ctx)
|
||||
{
|
||||
Cx* cx = http_ctx_user_ctx(ctx);
|
||||
|
||||
const char* body_str = http_ctx_req_body(ctx);
|
||||
const char* body_str = http_ctx_req_body_str(ctx);
|
||||
|
||||
JsonValue* body_json = json_parse(body_str, strlen(body_str));
|
||||
|
||||
|
@ -67,3 +67,10 @@ DbRes db_receipt_prices(
|
||||
/// `products` field is an out parameter.
|
||||
/// Expects `products` to be constructed.
|
||||
DbRes db_receipt_products(Db* db, ProductVec* products, int64_t receipt_id);
|
||||
|
||||
DbRes db_product_image_insert(
|
||||
Db* db, int64_t product_id, const uint8_t* data, size_t data_size);
|
||||
/// `data` and `data_size` are out parameters.
|
||||
/// `*data` should be freed.
|
||||
DbRes db_product_image_with_product_id(
|
||||
Db* db, uint8_t** data, size_t* data_size, int64_t product_id);
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include <sqlite3.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define REPORT_SQLITE3_ERROR() \
|
||||
fprintf(stderr, \
|
||||
@ -925,3 +927,86 @@ l0_return:
|
||||
DISCONNECT;
|
||||
return res;
|
||||
}
|
||||
|
||||
DbRes db_product_image_insert(
|
||||
Db* db, int64_t product_id, const uint8_t* data, size_t data_size)
|
||||
{
|
||||
sqlite3* connection;
|
||||
CONNECT;
|
||||
DbRes res;
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
int prepare_res = sqlite3_prepare_v2(connection,
|
||||
"INSERT INTO product_images (product, data) "
|
||||
"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_blob64(stmt, 2, data, data_size, NULL);
|
||||
|
||||
int step_res = sqlite3_step(stmt);
|
||||
if (step_res != SQLITE_DONE) {
|
||||
REPORT_SQLITE3_ERROR();
|
||||
res = DbRes_Error;
|
||||
goto l0_return;
|
||||
}
|
||||
|
||||
res = DbRes_Ok;
|
||||
l0_return:
|
||||
if (stmt)
|
||||
sqlite3_finalize(stmt);
|
||||
DISCONNECT;
|
||||
return res;
|
||||
}
|
||||
|
||||
DbRes db_product_image_with_product_id(
|
||||
Db* db, uint8_t** data, size_t* data_size, int64_t product_id)
|
||||
{
|
||||
|
||||
sqlite3* connection;
|
||||
CONNECT;
|
||||
DbRes res;
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
int prepare_res = sqlite3_prepare_v2(connection,
|
||||
"SELECT data"
|
||||
" FROM product_images WHERE product = ?",
|
||||
-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;
|
||||
}
|
||||
|
||||
*data_size = (size_t)sqlite3_column_bytes(stmt, 0);
|
||||
*data = malloc(*data_size);
|
||||
const void* db_data = sqlite3_column_blob(stmt, 0);
|
||||
memcpy(*data, db_data, *data_size);
|
||||
|
||||
res = DbRes_Ok;
|
||||
l0_return:
|
||||
if (stmt)
|
||||
sqlite3_finalize(stmt);
|
||||
DISCONNECT;
|
||||
return res;
|
||||
}
|
||||
|
@ -34,9 +34,13 @@ 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);
|
||||
const char* http_ctx_req_body_str(HttpCtx* ctx);
|
||||
const uint8_t* http_ctx_req_body(HttpCtx* ctx);
|
||||
size_t http_ctx_req_body_size(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);
|
||||
void http_ctx_respond_str(HttpCtx* ctx, int status, const char* body);
|
||||
void http_ctx_respond(
|
||||
HttpCtx* ctx, int status, const uint8_t* body, size_t body_size);
|
||||
|
||||
typedef struct HttpQueryParams HttpQueryParams;
|
||||
|
||||
|
@ -160,11 +160,21 @@ const char* http_ctx_req_query(HttpCtx* ctx)
|
||||
return ctx->req->query;
|
||||
}
|
||||
|
||||
const char* http_ctx_req_body(HttpCtx* ctx)
|
||||
const char* http_ctx_req_body_str(HttpCtx* ctx)
|
||||
{
|
||||
return (char*)ctx->req_body;
|
||||
}
|
||||
|
||||
const uint8_t* http_ctx_req_body(HttpCtx* ctx)
|
||||
{
|
||||
return ctx->req_body;
|
||||
}
|
||||
|
||||
size_t http_ctx_req_body_size(HttpCtx* ctx)
|
||||
{
|
||||
return ctx->req_body_size;
|
||||
}
|
||||
|
||||
void http_ctx_res_headers_set(HttpCtx* ctx, const char* key, const char* value)
|
||||
{
|
||||
char* key_copy = malloc(strlen(key) + 1);
|
||||
@ -175,11 +185,21 @@ void http_ctx_res_headers_set(HttpCtx* ctx, const char* key, const char* value)
|
||||
header_vec_push(&ctx->res_headers, (Header) { key_copy, value_copy });
|
||||
}
|
||||
|
||||
void http_ctx_respond(HttpCtx* ctx, int status, const char* body)
|
||||
void http_ctx_respond_str(HttpCtx* ctx, int status, const char* body)
|
||||
{
|
||||
http_ctx_respond(ctx, status, (const uint8_t*)body, strlen(body));
|
||||
}
|
||||
|
||||
void http_ctx_respond(
|
||||
HttpCtx* ctx, int status, const uint8_t* body, size_t body_size)
|
||||
{
|
||||
// https://httpwg.org/specs/rfc9112.html#persistent.tear-down
|
||||
http_ctx_res_headers_set(ctx, "Connection", "close");
|
||||
|
||||
char content_length[24] = { 0 };
|
||||
snprintf(content_length, 24 - 1, "%ld", body_size);
|
||||
http_ctx_res_headers_set(ctx, "Content-Length", content_length);
|
||||
|
||||
String res;
|
||||
string_construct(&res);
|
||||
|
||||
@ -199,12 +219,14 @@ void http_ctx_respond(HttpCtx* ctx, int status, const char* body)
|
||||
}
|
||||
string_push_str(&res, "\r\n");
|
||||
|
||||
string_push_str(&res, body);
|
||||
|
||||
ssize_t bytes_written = write(ctx->client->file, res.data, res.size);
|
||||
if (bytes_written != (ssize_t)res.size) {
|
||||
fprintf(stderr, "error: could not send response\n");
|
||||
fprintf(stderr, "error: could not send response header\n");
|
||||
}
|
||||
|
||||
string_destroy(&res);
|
||||
|
||||
bytes_written = write(ctx->client->file, body, body_size);
|
||||
if (bytes_written != (ssize_t)body_size) {
|
||||
fprintf(stderr, "error: could not send response body\n");
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,8 @@ struct HttpServer {
|
||||
struct HttpCtx {
|
||||
ClientConnection* client;
|
||||
const Request* req;
|
||||
const char* req_body;
|
||||
const uint8_t* req_body;
|
||||
size_t req_body_size;
|
||||
HeaderVec res_headers;
|
||||
void* user_ctx;
|
||||
};
|
||||
|
@ -96,7 +96,8 @@ void http_worker_handle_connection(Worker* worker, ClientConnection connection)
|
||||
HttpCtx handler_ctx = {
|
||||
.client = &client->connection,
|
||||
.req = &request,
|
||||
.req_body = (char*)request.body,
|
||||
.req_body = request.body,
|
||||
.req_body_size = request.body_size,
|
||||
.res_headers = { 0 },
|
||||
.user_ctx = worker->ctx->server->user_ctx,
|
||||
};
|
||||
|
@ -42,6 +42,10 @@ int main(void)
|
||||
server, "/api/products/create", route_post_products_create);
|
||||
http_server_post(
|
||||
server, "/api/products/update", route_post_products_update);
|
||||
http_server_post(
|
||||
server, "/api/products/set-image", route_post_products_set_image);
|
||||
http_server_get(
|
||||
server, "/api/products/image.png", route_get_products_image_png);
|
||||
|
||||
http_server_get(
|
||||
server, "/product_editor/index.html", route_get_product_editor_html);
|
||||
|
Loading…
x
Reference in New Issue
Block a user